1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/media_router/browser/android/media_router_android.h"
6 
7 #include <algorithm>
8 #include <string>
9 #include <utility>
10 
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/guid.h"
14 #include "base/logging.h"
15 #include "base/stl_util.h"
16 #include "components/media_router/browser/media_routes_observer.h"
17 #include "components/media_router/browser/media_sinks_observer.h"
18 #include "components/media_router/browser/route_message_observer.h"
19 #include "components/media_router/browser/route_message_util.h"
20 #include "components/media_router/common/route_request_result.h"
21 #include "url/gurl.h"
22 
23 namespace media_router {
24 
PresentationConnectionProxy(MediaRouterAndroid * media_router_android,const MediaRoute::Id & route_id)25 MediaRouterAndroid::PresentationConnectionProxy::PresentationConnectionProxy(
26     MediaRouterAndroid* media_router_android,
27     const MediaRoute::Id& route_id)
28     : media_router_android_(media_router_android), route_id_(route_id) {}
29 
30 MediaRouterAndroid::PresentationConnectionProxy::
31     ~PresentationConnectionProxy() = default;
32 
33 mojom::RoutePresentationConnectionPtr
Init()34 MediaRouterAndroid::PresentationConnectionProxy::Init() {
35   auto receiver = peer_.BindNewPipeAndPassReceiver();
36   peer_.set_disconnect_handler(
37       base::BindOnce(&MediaRouterAndroid::OnPresentationConnectionError,
38                      base::Unretained(media_router_android_), route_id_));
39   peer_->DidChangeState(blink::mojom::PresentationConnectionState::CONNECTED);
40   return mojom::RoutePresentationConnection::New(Bind(), std::move(receiver));
41 }
42 
OnMessage(blink::mojom::PresentationConnectionMessagePtr message)43 void MediaRouterAndroid::PresentationConnectionProxy::OnMessage(
44     blink::mojom::PresentationConnectionMessagePtr message) {
45   if (message->is_message())
46     media_router_android_->SendRouteMessage(route_id_, message->get_message());
47 }
48 
Terminate()49 void MediaRouterAndroid::PresentationConnectionProxy::Terminate() {
50   DCHECK(peer_);
51   peer_->DidChangeState(blink::mojom::PresentationConnectionState::TERMINATED);
52 }
53 
DidClose(blink::mojom::PresentationConnectionCloseReason reason)54 void MediaRouterAndroid::PresentationConnectionProxy::DidClose(
55     blink::mojom::PresentationConnectionCloseReason reason) {
56   auto& route_connections =
57       media_router_android_->presentation_connections_[route_id_];
58   DCHECK(!route_connections.empty());
59   base::EraseIf(route_connections, [this](const auto& connection) {
60     return connection.get() == this;
61   });
62 }
63 
64 mojo::PendingRemote<blink::mojom::PresentationConnection>
Bind()65 MediaRouterAndroid::PresentationConnectionProxy::Bind() {
66   mojo::PendingRemote<blink::mojom::PresentationConnection> connection_remote;
67   receiver_.Bind(connection_remote.InitWithNewPipeAndPassReceiver());
68   receiver_.set_disconnect_handler(
69       base::BindOnce(&MediaRouterAndroid::OnPresentationConnectionError,
70                      base::Unretained(media_router_android_), route_id_));
71   return connection_remote;
72 }
73 
SendMessage(const std::string & message)74 void MediaRouterAndroid::PresentationConnectionProxy::SendMessage(
75     const std::string& message) {
76   DCHECK(peer_);
77   peer_->OnMessage(
78       blink::mojom::PresentationConnectionMessage::NewMessage(message));
79 }
80 
MediaRouteRequest(const MediaSource & source,const std::string & presentation_id,MediaRouteResponseCallback callback)81 MediaRouterAndroid::MediaRouteRequest::MediaRouteRequest(
82     const MediaSource& source,
83     const std::string& presentation_id,
84     MediaRouteResponseCallback callback)
85     : media_source(source),
86       presentation_id(presentation_id),
87       callback(std::move(callback)) {}
88 
~MediaRouteRequest()89 MediaRouterAndroid::MediaRouteRequest::~MediaRouteRequest() {}
90 
MediaRouterAndroid()91 MediaRouterAndroid::MediaRouterAndroid()
92     : bridge_(new MediaRouterAndroidBridge(this)) {}
93 
~MediaRouterAndroid()94 MediaRouterAndroid::~MediaRouterAndroid() {}
95 
FindRouteBySource(const MediaSource::Id & source_id) const96 const MediaRoute* MediaRouterAndroid::FindRouteBySource(
97     const MediaSource::Id& source_id) const {
98   for (const auto& route : active_routes_) {
99     if (route.media_source().id() == source_id)
100       return &route;
101   }
102   return nullptr;
103 }
104 
CreateRoute(const MediaSource::Id & source_id,const MediaSink::Id & sink_id,const url::Origin & origin,content::WebContents * web_contents,MediaRouteResponseCallback callback,base::TimeDelta timeout,bool incognito)105 void MediaRouterAndroid::CreateRoute(const MediaSource::Id& source_id,
106                                      const MediaSink::Id& sink_id,
107                                      const url::Origin& origin,
108                                      content::WebContents* web_contents,
109                                      MediaRouteResponseCallback callback,
110                                      base::TimeDelta timeout,
111                                      bool incognito) {
112   DCHECK(callback);
113   // TODO(avayvod): Implement timeouts (crbug.com/583036).
114   std::string presentation_id = MediaRouterBase::CreatePresentationId();
115 
116   int route_request_id =
117       route_requests_.Add(std::make_unique<MediaRouteRequest>(
118           MediaSource(source_id), presentation_id, std::move(callback)));
119   bridge_->CreateRoute(source_id, sink_id, presentation_id, origin,
120                        web_contents, route_request_id);
121 }
122 
ConnectRouteByRouteId(const MediaSource::Id & source,const MediaRoute::Id & route_id,const url::Origin & origin,content::WebContents * web_contents,MediaRouteResponseCallback callback,base::TimeDelta timeout,bool incognito)123 void MediaRouterAndroid::ConnectRouteByRouteId(
124     const MediaSource::Id& source,
125     const MediaRoute::Id& route_id,
126     const url::Origin& origin,
127     content::WebContents* web_contents,
128     MediaRouteResponseCallback callback,
129     base::TimeDelta timeout,
130     bool incognito) {
131   NOTIMPLEMENTED();
132 }
133 
JoinRoute(const MediaSource::Id & source_id,const std::string & presentation_id,const url::Origin & origin,content::WebContents * web_contents,MediaRouteResponseCallback callback,base::TimeDelta timeout,bool incognito)134 void MediaRouterAndroid::JoinRoute(const MediaSource::Id& source_id,
135                                    const std::string& presentation_id,
136                                    const url::Origin& origin,
137                                    content::WebContents* web_contents,
138                                    MediaRouteResponseCallback callback,
139                                    base::TimeDelta timeout,
140                                    bool incognito) {
141   DCHECK(callback);
142   // TODO(avayvod): Implement timeouts (crbug.com/583036).
143   DVLOG(2) << "JoinRoute: " << source_id << ", " << presentation_id << ", "
144            << origin.GetURL().spec();
145 
146   int request_id = route_requests_.Add(std::make_unique<MediaRouteRequest>(
147       MediaSource(source_id), presentation_id, std::move(callback)));
148   bridge_->JoinRoute(source_id, presentation_id, origin, web_contents,
149                      request_id);
150 }
151 
TerminateRoute(const MediaRoute::Id & route_id)152 void MediaRouterAndroid::TerminateRoute(const MediaRoute::Id& route_id) {
153   bridge_->TerminateRoute(route_id);
154 }
155 
SendRouteMessage(const MediaRoute::Id & route_id,const std::string & message)156 void MediaRouterAndroid::SendRouteMessage(const MediaRoute::Id& route_id,
157                                           const std::string& message) {
158   bridge_->SendRouteMessage(route_id, message);
159 }
160 
SendRouteBinaryMessage(const MediaRoute::Id & route_id,std::unique_ptr<std::vector<uint8_t>> data)161 void MediaRouterAndroid::SendRouteBinaryMessage(
162     const MediaRoute::Id& route_id,
163     std::unique_ptr<std::vector<uint8_t>> data) {
164   // Binary messaging is not supported on Android.
165 }
166 
OnUserGesture()167 void MediaRouterAndroid::OnUserGesture() {}
168 
DetachRoute(const MediaRoute::Id & route_id)169 void MediaRouterAndroid::DetachRoute(const MediaRoute::Id& route_id) {
170   bridge_->DetachRoute(route_id);
171   RemoveRoute(route_id);
172   NotifyPresentationConnectionClose(
173       route_id, blink::mojom::PresentationConnectionCloseReason::CLOSED,
174       "Route closed normally");
175 }
176 
RegisterMediaSinksObserver(MediaSinksObserver * observer)177 bool MediaRouterAndroid::RegisterMediaSinksObserver(
178     MediaSinksObserver* observer) {
179   const std::string& source_id = observer->source()->id();
180   auto& observer_list = sinks_observers_[source_id];
181   if (!observer_list) {
182     observer_list = std::make_unique<MediaSinksObserverList>();
183   } else {
184     DCHECK(!observer_list->HasObserver(observer));
185   }
186 
187   observer_list->AddObserver(observer);
188   return bridge_->StartObservingMediaSinks(source_id);
189 }
190 
UnregisterMediaSinksObserver(MediaSinksObserver * observer)191 void MediaRouterAndroid::UnregisterMediaSinksObserver(
192     MediaSinksObserver* observer) {
193   const std::string& source_id = observer->source()->id();
194   auto it = sinks_observers_.find(source_id);
195   if (it == sinks_observers_.end() || !it->second->HasObserver(observer))
196     return;
197 
198   // If we are removing the final observer for the source, then stop
199   // observing sinks for it.
200   // might_have_observers() is reliable here on the assumption that this call
201   // is not inside the ObserverList iteration.
202   it->second->RemoveObserver(observer);
203   if (!it->second->might_have_observers()) {
204     sinks_observers_.erase(source_id);
205     bridge_->StopObservingMediaSinks(source_id);
206   }
207 }
208 
RegisterMediaRoutesObserver(MediaRoutesObserver * observer)209 void MediaRouterAndroid::RegisterMediaRoutesObserver(
210     MediaRoutesObserver* observer) {
211   DVLOG(2) << "Added MediaRoutesObserver: " << observer;
212   if (!observer->source_id().empty())
213     NOTIMPLEMENTED() << "Joinable routes query not implemented.";
214 
215   routes_observers_.AddObserver(observer);
216 }
217 
UnregisterMediaRoutesObserver(MediaRoutesObserver * observer)218 void MediaRouterAndroid::UnregisterMediaRoutesObserver(
219     MediaRoutesObserver* observer) {
220   if (!routes_observers_.HasObserver(observer))
221     return;
222   routes_observers_.RemoveObserver(observer);
223 }
224 
RegisterRouteMessageObserver(RouteMessageObserver * observer)225 void MediaRouterAndroid::RegisterRouteMessageObserver(
226     RouteMessageObserver* observer) {
227   NOTREACHED();
228 }
229 
UnregisterRouteMessageObserver(RouteMessageObserver * observer)230 void MediaRouterAndroid::UnregisterRouteMessageObserver(
231     RouteMessageObserver* observer) {
232   NOTREACHED();
233 }
234 
OnSinksReceived(const std::string & source_urn,const std::vector<MediaSink> & sinks)235 void MediaRouterAndroid::OnSinksReceived(const std::string& source_urn,
236                                          const std::vector<MediaSink>& sinks) {
237   auto it = sinks_observers_.find(source_urn);
238   if (it != sinks_observers_.end()) {
239     // TODO(imcheng): Pass origins to OnSinksUpdated (crbug.com/594858).
240     for (auto& observer : *it->second)
241       observer.OnSinksUpdated(sinks, std::vector<url::Origin>());
242   }
243 }
244 
OnRouteCreated(const MediaRoute::Id & route_id,const MediaSink::Id & sink_id,int route_request_id,bool is_local)245 void MediaRouterAndroid::OnRouteCreated(const MediaRoute::Id& route_id,
246                                         const MediaSink::Id& sink_id,
247                                         int route_request_id,
248                                         bool is_local) {
249   MediaRouteRequest* request = route_requests_.Lookup(route_request_id);
250   if (!request)
251     return;
252 
253   MediaRoute route(route_id, request->media_source, sink_id, std::string(),
254                    is_local, true);  // TODO(avayvod): Populate for_display.
255 
256   std::unique_ptr<RouteRequestResult> result =
257       RouteRequestResult::FromSuccess(route, request->presentation_id);
258   auto& presentation_connections = presentation_connections_[route_id];
259   presentation_connections.push_back(
260       std::make_unique<PresentationConnectionProxy>(this, route_id));
261   auto& presentation_connection = *presentation_connections.back();
262   std::move(request->callback).Run(presentation_connection.Init(), *result);
263 
264   route_requests_.Remove(route_request_id);
265 
266   active_routes_.push_back(route);
267   for (auto& observer : routes_observers_)
268     observer.OnRoutesUpdated(active_routes_, std::vector<MediaRoute::Id>());
269 }
270 
OnRouteRequestError(const std::string & error_text,int route_request_id)271 void MediaRouterAndroid::OnRouteRequestError(const std::string& error_text,
272                                              int route_request_id) {
273   MediaRouteRequest* request = route_requests_.Lookup(route_request_id);
274   if (!request)
275     return;
276 
277   // TODO(imcheng): Provide a more specific result code.
278   std::unique_ptr<RouteRequestResult> result = RouteRequestResult::FromError(
279       error_text, RouteRequestResult::UNKNOWN_ERROR);
280   std::move(request->callback).Run(nullptr, *result);
281 
282   route_requests_.Remove(route_request_id);
283 }
284 
OnRouteTerminated(const MediaRoute::Id & route_id)285 void MediaRouterAndroid::OnRouteTerminated(const MediaRoute::Id& route_id) {
286   auto entry = presentation_connections_.find(route_id);
287   if (entry != presentation_connections_.end()) {
288     // Note: Route-ID-to-presentation-ID mapping is done by route providers.
289     // Although the messages API (being deprecated) is based on route IDs,
290     // providers may use the same route for each presentation connection.  This
291     // would result in broadcasting provider messages to all presentation
292     // connections.  So although this loop may seem strange in the context of
293     // the Presentation API, it can't be avoided at the moment.
294     for (auto& connection : entry->second) {
295       connection->Terminate();
296     }
297   }
298   RemoveRoute(route_id);
299 }
300 
OnRouteClosed(const MediaRoute::Id & route_id,const base::Optional<std::string> & error)301 void MediaRouterAndroid::OnRouteClosed(
302     const MediaRoute::Id& route_id,
303     const base::Optional<std::string>& error) {
304   RemoveRoute(route_id);
305   // TODO(crbug.com/882690): When the sending context is destroyed, tell MRP to
306   // clean up the connection.
307   if (error.has_value()) {
308     NotifyPresentationConnectionClose(
309         route_id,
310         blink::mojom::PresentationConnectionCloseReason::CONNECTION_ERROR,
311         error.value());
312   } else {
313     NotifyPresentationConnectionClose(
314         route_id, blink::mojom::PresentationConnectionCloseReason::CLOSED,
315         "Remove route");
316   }
317 }
318 
OnMessage(const MediaRoute::Id & route_id,const std::string & message)319 void MediaRouterAndroid::OnMessage(const MediaRoute::Id& route_id,
320                                    const std::string& message) {
321   auto entry = presentation_connections_.find(route_id);
322   if (entry != presentation_connections_.end()) {
323     // Note: Route-ID-to-presentation-ID mapping is done by route providers.
324     // Although the messages API (being deprecated) is based on route IDs,
325     // providers may use the same route for each presentation connection.  This
326     // would result in broadcasting provider messages to all presentation
327     // connections.  So although this loop may seem strange in the context of
328     // the Presentation API, it can't be avoided at the moment.
329     DCHECK_EQ(1u, entry->second.size());
330     for (auto& connection : entry->second) {
331       connection->SendMessage(message);
332     }
333   }
334 }
335 
RemoveRoute(const MediaRoute::Id & route_id)336 void MediaRouterAndroid::RemoveRoute(const MediaRoute::Id& route_id) {
337   presentation_connections_.erase(route_id);
338   for (auto it = active_routes_.begin(); it != active_routes_.end(); ++it)
339     if (it->media_route_id() == route_id) {
340       active_routes_.erase(it);
341       break;
342     }
343 
344   for (auto& observer : routes_observers_)
345     observer.OnRoutesUpdated(active_routes_, std::vector<MediaRoute::Id>());
346 }
347 
348 std::unique_ptr<media::FlingingController>
GetFlingingController(const MediaRoute::Id & route_id)349 MediaRouterAndroid::GetFlingingController(const MediaRoute::Id& route_id) {
350   return bridge_->GetFlingingController(route_id);
351 }
352 
OnPresentationConnectionError(const std::string & route_id)353 void MediaRouterAndroid::OnPresentationConnectionError(
354     const std::string& route_id) {
355   presentation_connections_.erase(route_id);
356 }
357 
358 }  // namespace media_router
359