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.
5 #include "chrome/browser/media/android/router/media_router_android.h"
7 #include "base/android/jni_android.h"
8 #include "base/android/jni_string.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_vector.h"
12 #include "base/strings/stringprintf.h"
13 #include "chrome/browser/media/router/media_routes_observer.h"
14 #include "chrome/browser/media/router/media_sinks_observer.h"
15 #include "chrome/browser/media/router/presentation_session_messages_observer.h"
16 #include "jni/ChromeMediaRouter_jni.h"
19 using base::android::ConvertUTF8ToJavaString
;
20 using base::android::ConvertJavaStringToUTF8
;
21 using base::android::ScopedJavaLocalRef
;
22 using base::android::AttachCurrentThread
;
24 namespace media_router
{
26 MediaRouterAndroid::CreateMediaRouteRequest::CreateMediaRouteRequest(
27 const MediaSource
& source
,
28 const MediaSink
& sink
,
29 const std::string
& presentation_id
,
30 const std::vector
<MediaRouteResponseCallback
>& callbacks
)
31 : media_source(source
),
33 presentation_id(presentation_id
),
34 callbacks(callbacks
) {}
35 MediaRouterAndroid::CreateMediaRouteRequest::~CreateMediaRouteRequest() {}
37 MediaRouterAndroid::MediaRouterAndroid(content::BrowserContext
*) {
38 JNIEnv
* env
= base::android::AttachCurrentThread();
39 java_media_router_
.Reset(Java_ChromeMediaRouter_create(
41 reinterpret_cast<jlong
>(this),
42 base::android::GetApplicationContext()));
45 MediaRouterAndroid::~MediaRouterAndroid() {
49 bool MediaRouterAndroid::Register(JNIEnv
* env
) {
50 bool ret
= RegisterNativesImpl(env
);
51 DCHECK(g_ChromeMediaRouter_clazz
);
55 void MediaRouterAndroid::CreateRoute(
56 const MediaSource::Id
& source_id
,
57 const MediaSink::Id
& sink_id
,
60 const std::vector
<MediaRouteResponseCallback
>& callbacks
) {
61 if (!origin
.is_valid()) {
62 for (const MediaRouteResponseCallback
& callback
: callbacks
)
63 callback
.Run(nullptr, "", "Invalid origin");
67 // TODO(avayvod): unify presentation id generation code between platforms.
68 // https://crbug.com/522239
69 std::string
presentation_id("mr_");
70 presentation_id
+= base::GenerateGUID();
72 CreateMediaRouteRequest
* request
= new CreateMediaRouteRequest(
73 MediaSource(source_id
),
74 MediaSink(sink_id
, std::string(), MediaSink::GENERIC
),
77 int create_route_request_id
= create_route_requests_
.Add(request
);
79 JNIEnv
* env
= base::android::AttachCurrentThread();
80 ScopedJavaLocalRef
<jstring
> jsource_id
=
81 base::android::ConvertUTF8ToJavaString(env
, source_id
);
82 ScopedJavaLocalRef
<jstring
> jsink_id
=
83 base::android::ConvertUTF8ToJavaString(env
, sink_id
);
84 ScopedJavaLocalRef
<jstring
> jpresentation_id
=
85 base::android::ConvertUTF8ToJavaString(env
, presentation_id
);
87 Java_ChromeMediaRouter_createRoute(
89 java_media_router_
.obj(),
92 jpresentation_id
.obj(),
93 create_route_request_id
);
96 void MediaRouterAndroid::JoinRoute(
97 const MediaSource::Id
& source
,
98 const std::string
& presentation_id
,
101 const std::vector
<MediaRouteResponseCallback
>& callbacks
) {
102 GURL
source_url(source
);
103 DCHECK(source_url
.is_valid());
105 const MediaRoute
* matching_route
= nullptr;
106 for (const auto& route
: active_routes_
) {
107 GURL
route_source_url(route
.media_source().id());
108 DCHECK(route_source_url
.is_valid());
110 if (route_source_url
.scheme() != source_url
.scheme() ||
111 route_source_url
.username() != source_url
.username() ||
112 route_source_url
.password() != source_url
.password() ||
113 route_source_url
.host() != source_url
.host() ||
114 route_source_url
.port() != source_url
.port() ||
115 route_source_url
.path() != source_url
.path() ||
116 route_source_url
.query() != source_url
.query()) {
117 // Allow the ref() to be different.
121 // Since ref() could be different, use the existing route's source id.
122 const MediaSink::Id
& sink_id
= route
.media_sink_id();
123 const std::string
& potential_route_id
= base::StringPrintf(
125 presentation_id
.c_str(),
127 route
.media_source().id().c_str());
128 if (potential_route_id
== route
.media_route_id()) {
129 matching_route
= &route
;
134 if (!matching_route
) {
135 for (const auto& callback
: callbacks
)
136 callback
.Run(nullptr, std::string(), "No routes found");
140 for (const auto& callback
: callbacks
)
141 callback
.Run(matching_route
, presentation_id
, std::string());
144 void MediaRouterAndroid::CloseRoute(const MediaRoute::Id
& route_id
) {
145 JNIEnv
* env
= base::android::AttachCurrentThread();
146 ScopedJavaLocalRef
<jstring
> jroute_id
=
147 base::android::ConvertUTF8ToJavaString(env
, route_id
);
148 Java_ChromeMediaRouter_closeRoute(
149 env
, java_media_router_
.obj(), jroute_id
.obj());
152 void MediaRouterAndroid::SendRouteMessage(
153 const MediaRoute::Id
& route_id
,
154 const std::string
& message
,
155 const SendRouteMessageCallback
& callback
) {
156 int callback_id
= message_callbacks_
.Add(
157 new SendRouteMessageCallback(callback
));
158 JNIEnv
* env
= base::android::AttachCurrentThread();
159 ScopedJavaLocalRef
<jstring
> jroute_id
=
160 base::android::ConvertUTF8ToJavaString(env
, route_id
);
161 ScopedJavaLocalRef
<jstring
> jmessage
=
162 base::android::ConvertUTF8ToJavaString(env
, message
);
163 Java_ChromeMediaRouter_sendStringMessage(
165 java_media_router_
.obj(),
171 void MediaRouterAndroid::SendRouteBinaryMessage(
172 const MediaRoute::Id
& route_id
,
173 scoped_ptr
<std::vector
<uint8
>> data
,
174 const SendRouteMessageCallback
& callback
) {
178 void MediaRouterAndroid::AddIssue(const Issue
& issue
) {
182 void MediaRouterAndroid::ClearIssue(const Issue::Id
& issue_id
) {
186 void MediaRouterAndroid::OnPresentationSessionDetached(
187 const MediaRoute::Id
& route_id
) {
191 void MediaRouterAndroid::RegisterMediaSinksObserver(
192 MediaSinksObserver
* observer
) {
193 const std::string
& source_id
= observer
->source().id();
194 base::ObserverList
<MediaSinksObserver
>* observer_list
=
195 sinks_observers_
.get(source_id
);
196 if (!observer_list
) {
197 observer_list
= new base::ObserverList
<MediaSinksObserver
>;
198 sinks_observers_
.add(source_id
, make_scoped_ptr(observer_list
));
200 DCHECK(!observer_list
->HasObserver(observer
));
203 observer_list
->AddObserver(observer
);
204 JNIEnv
* env
= base::android::AttachCurrentThread();
205 ScopedJavaLocalRef
<jstring
> jsource_id
=
206 base::android::ConvertUTF8ToJavaString(env
, source_id
);
207 Java_ChromeMediaRouter_startObservingMediaSinks(
208 env
, java_media_router_
.obj(), jsource_id
.obj());
211 void MediaRouterAndroid::UnregisterMediaSinksObserver(
212 MediaSinksObserver
* observer
) {
213 const std::string
& source_id
= observer
->source().id();
214 auto* observer_list
= sinks_observers_
.get(source_id
);
215 if (!observer_list
|| !observer_list
->HasObserver(observer
))
218 // If we are removing the final observer for the source, then stop
219 // observing sinks for it.
220 // might_have_observers() is reliable here on the assumption that this call
221 // is not inside the ObserverList iteration.
222 observer_list
->RemoveObserver(observer
);
223 if (!observer_list
->might_have_observers()) {
224 sinks_observers_
.erase(source_id
);
225 JNIEnv
* env
= base::android::AttachCurrentThread();
226 ScopedJavaLocalRef
<jstring
> jsource_id
=
227 base::android::ConvertUTF8ToJavaString(env
, source_id
);
228 Java_ChromeMediaRouter_stopObservingMediaSinks(
229 env
, java_media_router_
.obj(), jsource_id
.obj());
233 void MediaRouterAndroid::RegisterMediaRoutesObserver(
234 MediaRoutesObserver
* observer
) {
235 DVLOG(2) << "Added MediaRoutesObserver: " << observer
;
236 routes_observers_
.AddObserver(observer
);
239 void MediaRouterAndroid::UnregisterMediaRoutesObserver(
240 MediaRoutesObserver
* observer
) {
241 if (!routes_observers_
.HasObserver(observer
))
243 routes_observers_
.RemoveObserver(observer
);
246 void MediaRouterAndroid::RegisterIssuesObserver(IssuesObserver
* observer
) {
250 void MediaRouterAndroid::UnregisterIssuesObserver(IssuesObserver
* observer
) {
254 void MediaRouterAndroid::RegisterPresentationSessionMessagesObserver(
255 PresentationSessionMessagesObserver
* observer
) {
256 const MediaRoute::Id
& route_id
= observer
->route_id();
257 auto* observer_list
= messages_observers_
.get(route_id
);
258 if (!observer_list
) {
259 observer_list
= new base::ObserverList
<PresentationSessionMessagesObserver
>;
260 messages_observers_
.add(route_id
, make_scoped_ptr(observer_list
));
262 DCHECK(!observer_list
->HasObserver(observer
));
265 observer_list
->AddObserver(observer
);
268 void MediaRouterAndroid::UnregisterPresentationSessionMessagesObserver(
269 PresentationSessionMessagesObserver
* observer
) {
270 const MediaRoute::Id
& route_id
= observer
->route_id();
271 auto* observer_list
= messages_observers_
.get(route_id
);
272 DCHECK(observer_list
->HasObserver(observer
));
274 observer_list
->RemoveObserver(observer
);
275 if (!observer_list
->might_have_observers())
276 messages_observers_
.erase(route_id
);
279 void MediaRouterAndroid::OnSinksReceived(
284 std::vector
<MediaSink
> sinks_converted
;
285 sinks_converted
.reserve(jcount
);
286 for (int i
= 0; i
< jcount
; ++i
) {
287 ScopedJavaLocalRef
<jstring
> jsink_urn
=
288 Java_ChromeMediaRouter_getSinkUrn(
289 env
, java_media_router_
.obj(), jsource_urn
, i
);
290 ScopedJavaLocalRef
<jstring
> jsink_name
=
291 Java_ChromeMediaRouter_getSinkName(
292 env
, java_media_router_
.obj(), jsource_urn
, i
);
293 sinks_converted
.push_back(
294 MediaSink(ConvertJavaStringToUTF8(env
, jsink_urn
.obj()),
295 ConvertJavaStringToUTF8(env
, jsink_name
.obj()),
296 MediaSink::GENERIC
));
299 std::string source_urn
= ConvertJavaStringToUTF8(env
, jsource_urn
);
300 auto it
= sinks_observers_
.find(source_urn
);
301 if (it
!= sinks_observers_
.end()) {
302 FOR_EACH_OBSERVER(MediaSinksObserver
, *it
->second
,
303 OnSinksReceived(sinks_converted
));
307 void MediaRouterAndroid::OnRouteCreated(
310 jstring jmedia_route_id
,
311 jint jcreate_route_request_id
,
312 jboolean jis_local
) {
313 CreateMediaRouteRequest
* request
=
314 create_route_requests_
.Lookup(jcreate_route_request_id
);
318 MediaRoute
route(ConvertJavaStringToUTF8(env
, jmedia_route_id
),
319 request
->media_source
, request
->media_sink
.id(),
320 std::string(), jis_local
, std::string(),
321 true); // TODO(avayvod): Populate for_display.
323 for (const MediaRouteResponseCallback
& callback
: request
->callbacks
)
324 callback
.Run(&route
, request
->presentation_id
, std::string());
326 create_route_requests_
.Remove(jcreate_route_request_id
);
328 active_routes_
.push_back(route
);
329 FOR_EACH_OBSERVER(MediaRoutesObserver
, routes_observers_
,
330 OnRoutesUpdated(active_routes_
));
333 void MediaRouterAndroid::OnRouteCreationError(
337 jint jcreate_route_request_id
) {
338 CreateMediaRouteRequest
* request
=
339 create_route_requests_
.Lookup(jcreate_route_request_id
);
343 std::string error_text
= ConvertJavaStringToUTF8(env
, jerror_text
);
345 for (const MediaRouteResponseCallback
& callback
: request
->callbacks
)
346 callback
.Run(nullptr, std::string(), error_text
);
348 create_route_requests_
.Remove(jcreate_route_request_id
);
351 void MediaRouterAndroid::OnRouteClosed(JNIEnv
* env
,
353 jstring jmedia_route_id
) {
354 MediaRoute::Id route_id
= ConvertJavaStringToUTF8(env
, jmedia_route_id
);
355 for (auto it
= active_routes_
.begin(); it
!= active_routes_
.end(); ++it
)
356 if (it
->media_route_id() == route_id
) {
357 active_routes_
.erase(it
);
361 FOR_EACH_OBSERVER(MediaRoutesObserver
, routes_observers_
,
362 OnRoutesUpdated(active_routes_
));
365 void MediaRouterAndroid::OnMessageSentResult(
366 JNIEnv
* env
, jobject obj
, jboolean jsuccess
, jint jcallback_id
) {
367 SendRouteMessageCallback
* callback
= message_callbacks_
.Lookup(jcallback_id
);
368 callback
->Run(jsuccess
);
369 message_callbacks_
.Remove(jcallback_id
);
372 // Notifies the media router about a message received from the media route.
373 void MediaRouterAndroid::OnMessage(
374 JNIEnv
* env
, jobject obj
, jstring jmedia_route_id
, jstring jmessage
) {
375 MediaRoute::Id route_id
= ConvertJavaStringToUTF8(env
, jmedia_route_id
);
376 auto* observer_list
= messages_observers_
.get(route_id
);
380 ScopedVector
<content::PresentationSessionMessage
> session_messages
;
381 scoped_ptr
<content::PresentationSessionMessage
> message(
382 new content::PresentationSessionMessage(content::TEXT
));
383 message
->message
= ConvertJavaStringToUTF8(env
, jmessage
);
384 session_messages
.push_back(message
.Pass());
386 FOR_EACH_OBSERVER(PresentationSessionMessagesObserver
, *observer_list
,
387 OnMessagesReceived(session_messages
, true));
390 } // namespace media_router