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::ClearIssue(const Issue::Id
& issue_id
) {
182 void MediaRouterAndroid::OnPresentationSessionDetached(
183 const MediaRoute::Id
& route_id
) {
187 void MediaRouterAndroid::RegisterMediaSinksObserver(
188 MediaSinksObserver
* observer
) {
189 const std::string
& source_id
= observer
->source().id();
190 base::ObserverList
<MediaSinksObserver
>* observer_list
=
191 sinks_observers_
.get(source_id
);
192 if (!observer_list
) {
193 observer_list
= new base::ObserverList
<MediaSinksObserver
>;
194 sinks_observers_
.add(source_id
, make_scoped_ptr(observer_list
));
196 DCHECK(!observer_list
->HasObserver(observer
));
199 observer_list
->AddObserver(observer
);
200 JNIEnv
* env
= base::android::AttachCurrentThread();
201 ScopedJavaLocalRef
<jstring
> jsource_id
=
202 base::android::ConvertUTF8ToJavaString(env
, source_id
);
203 Java_ChromeMediaRouter_startObservingMediaSinks(
204 env
, java_media_router_
.obj(), jsource_id
.obj());
207 void MediaRouterAndroid::UnregisterMediaSinksObserver(
208 MediaSinksObserver
* observer
) {
209 const std::string
& source_id
= observer
->source().id();
210 auto* observer_list
= sinks_observers_
.get(source_id
);
211 if (!observer_list
|| !observer_list
->HasObserver(observer
))
214 // If we are removing the final observer for the source, then stop
215 // observing sinks for it.
216 // might_have_observers() is reliable here on the assumption that this call
217 // is not inside the ObserverList iteration.
218 observer_list
->RemoveObserver(observer
);
219 if (!observer_list
->might_have_observers()) {
220 sinks_observers_
.erase(source_id
);
221 JNIEnv
* env
= base::android::AttachCurrentThread();
222 ScopedJavaLocalRef
<jstring
> jsource_id
=
223 base::android::ConvertUTF8ToJavaString(env
, source_id
);
224 Java_ChromeMediaRouter_stopObservingMediaSinks(
225 env
, java_media_router_
.obj(), jsource_id
.obj());
229 void MediaRouterAndroid::RegisterMediaRoutesObserver(
230 MediaRoutesObserver
* observer
) {
231 DVLOG(2) << "Added MediaRoutesObserver: " << observer
;
232 routes_observers_
.AddObserver(observer
);
235 void MediaRouterAndroid::UnregisterMediaRoutesObserver(
236 MediaRoutesObserver
* observer
) {
237 if (!routes_observers_
.HasObserver(observer
))
239 routes_observers_
.RemoveObserver(observer
);
242 void MediaRouterAndroid::RegisterIssuesObserver(IssuesObserver
* observer
) {
246 void MediaRouterAndroid::UnregisterIssuesObserver(IssuesObserver
* observer
) {
250 void MediaRouterAndroid::RegisterPresentationSessionMessagesObserver(
251 PresentationSessionMessagesObserver
* observer
) {
252 const MediaRoute::Id
& route_id
= observer
->route_id();
253 auto* observer_list
= messages_observers_
.get(route_id
);
254 if (!observer_list
) {
255 observer_list
= new base::ObserverList
<PresentationSessionMessagesObserver
>;
256 messages_observers_
.add(route_id
, make_scoped_ptr(observer_list
));
258 DCHECK(!observer_list
->HasObserver(observer
));
261 observer_list
->AddObserver(observer
);
264 void MediaRouterAndroid::UnregisterPresentationSessionMessagesObserver(
265 PresentationSessionMessagesObserver
* observer
) {
266 const MediaRoute::Id
& route_id
= observer
->route_id();
267 auto* observer_list
= messages_observers_
.get(route_id
);
268 DCHECK(observer_list
->HasObserver(observer
));
270 observer_list
->RemoveObserver(observer
);
271 if (!observer_list
->might_have_observers())
272 messages_observers_
.erase(route_id
);
275 void MediaRouterAndroid::OnSinksReceived(
280 std::vector
<MediaSink
> sinks_converted
;
281 sinks_converted
.reserve(jcount
);
282 for (int i
= 0; i
< jcount
; ++i
) {
283 ScopedJavaLocalRef
<jstring
> jsink_urn
=
284 Java_ChromeMediaRouter_getSinkUrn(
285 env
, java_media_router_
.obj(), jsource_urn
, i
);
286 ScopedJavaLocalRef
<jstring
> jsink_name
=
287 Java_ChromeMediaRouter_getSinkName(
288 env
, java_media_router_
.obj(), jsource_urn
, i
);
289 sinks_converted
.push_back(
290 MediaSink(ConvertJavaStringToUTF8(env
, jsink_urn
.obj()),
291 ConvertJavaStringToUTF8(env
, jsink_name
.obj()),
292 MediaSink::GENERIC
));
295 std::string source_urn
= ConvertJavaStringToUTF8(env
, jsource_urn
);
296 auto it
= sinks_observers_
.find(source_urn
);
297 if (it
!= sinks_observers_
.end()) {
298 FOR_EACH_OBSERVER(MediaSinksObserver
, *it
->second
,
299 OnSinksReceived(sinks_converted
));
303 void MediaRouterAndroid::OnRouteCreated(
306 jstring jmedia_route_id
,
307 jint jcreate_route_request_id
,
308 jboolean jis_local
) {
309 CreateMediaRouteRequest
* request
=
310 create_route_requests_
.Lookup(jcreate_route_request_id
);
314 MediaRoute
route(ConvertJavaStringToUTF8(env
, jmedia_route_id
),
315 request
->media_source
, request
->media_sink
.id(),
316 std::string(), jis_local
, std::string(),
317 true); // TODO(avayvod): Populate for_display.
319 for (const MediaRouteResponseCallback
& callback
: request
->callbacks
)
320 callback
.Run(&route
, request
->presentation_id
, std::string());
322 create_route_requests_
.Remove(jcreate_route_request_id
);
324 active_routes_
.push_back(route
);
325 FOR_EACH_OBSERVER(MediaRoutesObserver
, routes_observers_
,
326 OnRoutesUpdated(active_routes_
));
329 void MediaRouterAndroid::OnRouteCreationError(
333 jint jcreate_route_request_id
) {
334 CreateMediaRouteRequest
* request
=
335 create_route_requests_
.Lookup(jcreate_route_request_id
);
339 std::string error_text
= ConvertJavaStringToUTF8(env
, jerror_text
);
341 for (const MediaRouteResponseCallback
& callback
: request
->callbacks
)
342 callback
.Run(nullptr, std::string(), error_text
);
344 create_route_requests_
.Remove(jcreate_route_request_id
);
347 void MediaRouterAndroid::OnRouteClosed(JNIEnv
* env
,
349 jstring jmedia_route_id
) {
350 MediaRoute::Id route_id
= ConvertJavaStringToUTF8(env
, jmedia_route_id
);
351 for (auto it
= active_routes_
.begin(); it
!= active_routes_
.end(); ++it
)
352 if (it
->media_route_id() == route_id
) {
353 active_routes_
.erase(it
);
357 FOR_EACH_OBSERVER(MediaRoutesObserver
, routes_observers_
,
358 OnRoutesUpdated(active_routes_
));
361 void MediaRouterAndroid::OnMessageSentResult(
362 JNIEnv
* env
, jobject obj
, jboolean jsuccess
, jint jcallback_id
) {
363 SendRouteMessageCallback
* callback
= message_callbacks_
.Lookup(jcallback_id
);
364 callback
->Run(jsuccess
);
365 message_callbacks_
.Remove(jcallback_id
);
368 // Notifies the media router about a message received from the media route.
369 void MediaRouterAndroid::OnMessage(
370 JNIEnv
* env
, jobject obj
, jstring jmedia_route_id
, jstring jmessage
) {
371 MediaRoute::Id route_id
= ConvertJavaStringToUTF8(env
, jmedia_route_id
);
372 auto* observer_list
= messages_observers_
.get(route_id
);
376 ScopedVector
<content::PresentationSessionMessage
> session_messages
;
377 scoped_ptr
<content::PresentationSessionMessage
> message(
378 new content::PresentationSessionMessage(content::TEXT
));
379 message
->message
= ConvertJavaStringToUTF8(env
, jmessage
);
380 session_messages
.push_back(message
.Pass());
382 FOR_EACH_OBSERVER(PresentationSessionMessagesObserver
, *observer_list
,
383 OnMessagesReceived(session_messages
, true));
386 } // namespace media_router