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