1 // Copyright (c) 2013 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 "media/base/android/media_drm_bridge.h"
9 #include "base/android/build_info.h"
10 #include "base/android/jni_array.h"
11 #include "base/android/jni_string.h"
12 #include "base/callback_helpers.h"
13 #include "base/containers/hash_tables.h"
14 #include "base/lazy_instance.h"
15 #include "base/location.h"
16 #include "base/logging.h"
17 #include "base/single_thread_task_runner.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string_util.h"
20 #include "base/sys_byteorder.h"
21 #include "base/sys_info.h"
22 #include "base/thread_task_runner_handle.h"
23 #include "jni/MediaDrmBridge_jni.h"
24 #include "media/base/android/media_client_android.h"
25 #include "media/base/android/media_drm_bridge_delegate.h"
26 #include "media/base/cdm_key_information.h"
28 #include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR.
30 using base::android::AttachCurrentThread
;
31 using base::android::ConvertUTF8ToJavaString
;
32 using base::android::ConvertJavaStringToUTF8
;
33 using base::android::JavaByteArrayToByteVector
;
34 using base::android::ScopedJavaLocalRef
;
40 // DrmBridge supports session expiration event but doesn't provide detailed
41 // status for each key ID, which is required by the EME spec. Use a dummy key ID
42 // here to report session expiration info.
43 const char kDummyKeyId
[] = "Dummy Key Id";
45 // Returns string session ID from jbyteArray (byte[] in Java).
46 std::string
GetSessionId(JNIEnv
* env
, jbyteArray j_session_id
) {
47 std::vector
<uint8
> session_id_vector
;
48 JavaByteArrayToByteVector(env
, j_session_id
, &session_id_vector
);
49 return std::string(session_id_vector
.begin(), session_id_vector
.end());
52 const uint8 kWidevineUuid
[16] = {
53 0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE,
54 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED };
56 // Convert |init_data_type| to a string supported by MediaDRM.
57 // "audio"/"video" does not matter, so use "video".
58 std::string
ConvertInitDataType(media::EmeInitDataType init_data_type
) {
59 // TODO(jrummell/xhwang): EME init data types like "webm" and "cenc" are
60 // supported in API level >=21 for Widevine key system. Switch to use those
61 // strings when they are officially supported in Android for all key systems.
62 switch (init_data_type
) {
63 case media::EmeInitDataType::WEBM
:
65 case media::EmeInitDataType::CENC
:
67 case media::EmeInitDataType::KEYIDS
:
75 class KeySystemManager
{
78 UUID
GetUUID(const std::string
& key_system
);
79 std::vector
<std::string
> GetPlatformKeySystemNames();
82 using KeySystemUuidMap
= MediaClientAndroid::KeySystemUuidMap
;
84 KeySystemUuidMap key_system_uuid_map_
;
86 DISALLOW_COPY_AND_ASSIGN(KeySystemManager
);
89 KeySystemManager::KeySystemManager() {
90 // Widevine is always supported in Android.
91 key_system_uuid_map_
[kWidevineKeySystem
] =
92 UUID(kWidevineUuid
, kWidevineUuid
+ arraysize(kWidevineUuid
));
93 MediaClientAndroid
* client
= GetMediaClientAndroid();
95 client
->AddKeySystemUUIDMappings(&key_system_uuid_map_
);
98 UUID
KeySystemManager::GetUUID(const std::string
& key_system
) {
99 KeySystemUuidMap::iterator it
= key_system_uuid_map_
.find(key_system
);
100 if (it
== key_system_uuid_map_
.end())
105 std::vector
<std::string
> KeySystemManager::GetPlatformKeySystemNames() {
106 std::vector
<std::string
> key_systems
;
107 for (KeySystemUuidMap::iterator it
= key_system_uuid_map_
.begin();
108 it
!= key_system_uuid_map_
.end(); ++it
) {
109 // Rule out the key system handled by Chrome explicitly.
110 if (it
->first
!= kWidevineKeySystem
)
111 key_systems
.push_back(it
->first
);
116 base::LazyInstance
<KeySystemManager
>::Leaky g_key_system_manager
=
117 LAZY_INSTANCE_INITIALIZER
;
119 // Checks whether |key_system| is supported with |container_mime_type|. Only
120 // checks |key_system| support if |container_mime_type| is empty.
121 // TODO(xhwang): The |container_mime_type| is not the same as contentType in
122 // the EME spec. Revisit this once the spec issue with initData type is
124 bool IsKeySystemSupportedWithTypeImpl(const std::string
& key_system
,
125 const std::string
& container_mime_type
) {
126 if (!MediaDrmBridge::IsAvailable())
129 UUID scheme_uuid
= g_key_system_manager
.Get().GetUUID(key_system
);
130 if (scheme_uuid
.empty())
133 JNIEnv
* env
= AttachCurrentThread();
134 ScopedJavaLocalRef
<jbyteArray
> j_scheme_uuid
=
135 base::android::ToJavaByteArray(env
, &scheme_uuid
[0], scheme_uuid
.size());
136 ScopedJavaLocalRef
<jstring
> j_container_mime_type
=
137 ConvertUTF8ToJavaString(env
, container_mime_type
);
138 return Java_MediaDrmBridge_isCryptoSchemeSupported(
139 env
, j_scheme_uuid
.obj(), j_container_mime_type
.obj());
142 MediaDrmBridge::SecurityLevel
GetSecurityLevelFromString(
143 const std::string
& security_level_str
) {
144 if (0 == security_level_str
.compare("L1"))
145 return MediaDrmBridge::SECURITY_LEVEL_1
;
146 if (0 == security_level_str
.compare("L3"))
147 return MediaDrmBridge::SECURITY_LEVEL_3
;
148 DCHECK(security_level_str
.empty());
149 return MediaDrmBridge::SECURITY_LEVEL_NONE
;
152 std::string
GetSecurityLevelString(
153 MediaDrmBridge::SecurityLevel security_level
) {
154 switch (security_level
) {
155 case MediaDrmBridge::SECURITY_LEVEL_NONE
:
157 case MediaDrmBridge::SECURITY_LEVEL_1
:
159 case MediaDrmBridge::SECURITY_LEVEL_3
:
168 bool MediaDrmBridge::IsAvailable() {
169 if (base::android::BuildInfo::GetInstance()->sdk_int() < 19)
172 int32 os_major_version
= 0;
173 int32 os_minor_version
= 0;
174 int32 os_bugfix_version
= 0;
175 base::SysInfo::OperatingSystemVersionNumbers(&os_major_version
,
178 if (os_major_version
== 4 && os_minor_version
== 4 && os_bugfix_version
== 0)
184 // TODO(ddorwin): This is specific to Widevine. http://crbug.com/459400
186 bool MediaDrmBridge::IsSecureDecoderRequired(SecurityLevel security_level
) {
187 DCHECK(IsAvailable());
188 return SECURITY_LEVEL_1
== security_level
;
192 std::vector
<std::string
> MediaDrmBridge::GetPlatformKeySystemNames() {
193 return g_key_system_manager
.Get().GetPlatformKeySystemNames();
197 bool MediaDrmBridge::IsKeySystemSupported(const std::string
& key_system
) {
198 DCHECK(!key_system
.empty());
199 return IsKeySystemSupportedWithTypeImpl(key_system
, "");
203 bool MediaDrmBridge::IsKeySystemSupportedWithType(
204 const std::string
& key_system
,
205 const std::string
& container_mime_type
) {
206 DCHECK(!key_system
.empty() && !container_mime_type
.empty());
207 return IsKeySystemSupportedWithTypeImpl(key_system
, container_mime_type
);
210 bool MediaDrmBridge::RegisterMediaDrmBridge(JNIEnv
* env
) {
211 return RegisterNativesImpl(env
);
214 MediaDrmBridge::MediaDrmBridge(
215 const std::vector
<uint8
>& scheme_uuid
,
216 const SessionMessageCB
& session_message_cb
,
217 const SessionClosedCB
& session_closed_cb
,
218 const LegacySessionErrorCB
& legacy_session_error_cb
,
219 const SessionKeysChangeCB
& session_keys_change_cb
)
220 : scheme_uuid_(scheme_uuid
),
221 session_message_cb_(session_message_cb
),
222 session_closed_cb_(session_closed_cb
),
223 legacy_session_error_cb_(legacy_session_error_cb
),
224 session_keys_change_cb_(session_keys_change_cb
) {
225 JNIEnv
* env
= AttachCurrentThread();
228 ScopedJavaLocalRef
<jbyteArray
> j_scheme_uuid
=
229 base::android::ToJavaByteArray(env
, &scheme_uuid
[0], scheme_uuid
.size());
230 j_media_drm_
.Reset(Java_MediaDrmBridge_create(
231 env
, j_scheme_uuid
.obj(), reinterpret_cast<intptr_t>(this)));
234 MediaDrmBridge::~MediaDrmBridge() {
235 JNIEnv
* env
= AttachCurrentThread();
236 player_tracker_
.NotifyCdmUnset();
237 if (!j_media_drm_
.is_null())
238 Java_MediaDrmBridge_destroy(env
, j_media_drm_
.obj());
242 // TODO(xhwang): Enable SessionExpirationUpdateCB when it is supported.
243 scoped_ptr
<MediaDrmBridge
> MediaDrmBridge::Create(
244 const std::string
& key_system
,
245 const SessionMessageCB
& session_message_cb
,
246 const SessionClosedCB
& session_closed_cb
,
247 const LegacySessionErrorCB
& legacy_session_error_cb
,
248 const SessionKeysChangeCB
& session_keys_change_cb
,
249 const SessionExpirationUpdateCB
& /* session_expiration_update_cb */) {
250 scoped_ptr
<MediaDrmBridge
> media_drm_bridge
;
252 return media_drm_bridge
.Pass();
254 UUID scheme_uuid
= g_key_system_manager
.Get().GetUUID(key_system
);
255 if (scheme_uuid
.empty())
256 return media_drm_bridge
.Pass();
258 media_drm_bridge
.reset(
259 new MediaDrmBridge(scheme_uuid
, session_message_cb
, session_closed_cb
,
260 legacy_session_error_cb
, session_keys_change_cb
));
262 if (media_drm_bridge
->j_media_drm_
.is_null())
263 media_drm_bridge
.reset();
265 return media_drm_bridge
.Pass();
269 scoped_ptr
<MediaDrmBridge
> MediaDrmBridge::CreateWithoutSessionSupport(
270 const std::string
& key_system
) {
271 return MediaDrmBridge::Create(
272 key_system
, SessionMessageCB(), SessionClosedCB(), LegacySessionErrorCB(),
273 SessionKeysChangeCB(), SessionExpirationUpdateCB());
276 bool MediaDrmBridge::SetSecurityLevel(SecurityLevel security_level
) {
277 if (security_level
!= SECURITY_LEVEL_NONE
&&
278 !std::equal(scheme_uuid_
.begin(), scheme_uuid_
.end(), kWidevineUuid
)) {
279 NOTREACHED() << "Widevine security level " << security_level
280 << "used with another key system";
284 JNIEnv
* env
= AttachCurrentThread();
286 std::string security_level_str
= GetSecurityLevelString(security_level
);
287 if (security_level_str
.empty())
290 ScopedJavaLocalRef
<jstring
> j_security_level
=
291 ConvertUTF8ToJavaString(env
, security_level_str
);
292 return Java_MediaDrmBridge_setSecurityLevel(
293 env
, j_media_drm_
.obj(), j_security_level
.obj());
296 void MediaDrmBridge::SetServerCertificate(
297 const std::vector
<uint8_t>& certificate
,
298 scoped_ptr
<media::SimpleCdmPromise
> promise
) {
299 DCHECK(!certificate
.empty());
301 JNIEnv
* env
= AttachCurrentThread();
302 ScopedJavaLocalRef
<jbyteArray
> j_certificate
;
303 if (Java_MediaDrmBridge_setServerCertificate(env
, j_media_drm_
.obj(),
304 j_certificate
.obj())) {
307 promise
->reject(INVALID_ACCESS_ERROR
, 0, "Set server certificate failed.");
311 void MediaDrmBridge::CreateSessionAndGenerateRequest(
312 SessionType session_type
,
313 media::EmeInitDataType init_data_type
,
314 const std::vector
<uint8_t>& init_data
,
315 scoped_ptr
<media::NewSessionCdmPromise
> promise
) {
316 DVLOG(1) << __FUNCTION__
;
318 if (session_type
!= media::MediaKeys::TEMPORARY_SESSION
) {
319 NOTIMPLEMENTED() << "EME persistent sessions not yet supported on Android.";
320 promise
->reject(NOT_SUPPORTED_ERROR
, 0,
321 "Only the temporary session type is supported.");
325 JNIEnv
* env
= AttachCurrentThread();
326 ScopedJavaLocalRef
<jbyteArray
> j_init_data
;
327 ScopedJavaLocalRef
<jobjectArray
> j_optional_parameters
;
329 MediaClientAndroid
* client
= GetMediaClientAndroid();
331 MediaDrmBridgeDelegate
* delegate
=
332 client
->GetMediaDrmBridgeDelegate(scheme_uuid_
);
334 std::vector
<uint8
> init_data_from_delegate
;
335 std::vector
<std::string
> optional_parameters_from_delegate
;
336 if (!delegate
->OnCreateSession(init_data_type
, init_data
,
337 &init_data_from_delegate
,
338 &optional_parameters_from_delegate
)) {
339 promise
->reject(INVALID_ACCESS_ERROR
, 0, "Invalid init data.");
341 if (!init_data_from_delegate
.empty()) {
342 j_init_data
= base::android::ToJavaByteArray(
343 env
, vector_as_array(&init_data_from_delegate
),
344 init_data_from_delegate
.size());
346 if (!optional_parameters_from_delegate
.empty()) {
347 j_optional_parameters
= base::android::ToJavaArrayOfStrings(
348 env
, optional_parameters_from_delegate
);
353 if (j_init_data
.is_null()) {
354 j_init_data
= base::android::ToJavaByteArray(
355 env
, vector_as_array(&init_data
), init_data
.size());
358 ScopedJavaLocalRef
<jstring
> j_mime
=
359 ConvertUTF8ToJavaString(env
, ConvertInitDataType(init_data_type
));
360 uint32_t promise_id
= cdm_promise_adapter_
.SavePromise(promise
.Pass());
361 Java_MediaDrmBridge_createSessionFromNative(env
, j_media_drm_
.obj(),
362 j_init_data
.obj(), j_mime
.obj(),
363 j_optional_parameters
.obj(),
367 void MediaDrmBridge::LoadSession(
368 SessionType session_type
,
369 const std::string
& session_id
,
370 scoped_ptr
<media::NewSessionCdmPromise
> promise
) {
371 NOTIMPLEMENTED() << "EME persistent sessions not yet supported on Android.";
372 promise
->reject(NOT_SUPPORTED_ERROR
, 0, "LoadSession() is not supported.");
375 void MediaDrmBridge::UpdateSession(
376 const std::string
& session_id
,
377 const std::vector
<uint8_t>& response
,
378 scoped_ptr
<media::SimpleCdmPromise
> promise
) {
379 DVLOG(1) << __FUNCTION__
;
381 JNIEnv
* env
= AttachCurrentThread();
382 ScopedJavaLocalRef
<jbyteArray
> j_response
= base::android::ToJavaByteArray(
383 env
, vector_as_array(&response
), response
.size());
384 ScopedJavaLocalRef
<jbyteArray
> j_session_id
= base::android::ToJavaByteArray(
385 env
, reinterpret_cast<const uint8_t*>(session_id
.data()),
387 uint32_t promise_id
= cdm_promise_adapter_
.SavePromise(promise
.Pass());
388 Java_MediaDrmBridge_updateSession(env
, j_media_drm_
.obj(), j_session_id
.obj(),
389 j_response
.obj(), promise_id
);
392 void MediaDrmBridge::CloseSession(const std::string
& session_id
,
393 scoped_ptr
<media::SimpleCdmPromise
> promise
) {
394 DVLOG(1) << __FUNCTION__
;
395 JNIEnv
* env
= AttachCurrentThread();
396 ScopedJavaLocalRef
<jbyteArray
> j_session_id
= base::android::ToJavaByteArray(
397 env
, reinterpret_cast<const uint8_t*>(session_id
.data()),
399 uint32_t promise_id
= cdm_promise_adapter_
.SavePromise(promise
.Pass());
400 Java_MediaDrmBridge_closeSession(env
, j_media_drm_
.obj(), j_session_id
.obj(),
404 void MediaDrmBridge::RemoveSession(
405 const std::string
& session_id
,
406 scoped_ptr
<media::SimpleCdmPromise
> promise
) {
407 NOTIMPLEMENTED() << "EME persistent sessions not yet supported on Android.";
408 promise
->reject(NOT_SUPPORTED_ERROR
, 0, "RemoveSession() is not supported.");
411 CdmContext
* MediaDrmBridge::GetCdmContext() {
416 int MediaDrmBridge::RegisterPlayer(const base::Closure
& new_key_cb
,
417 const base::Closure
& cdm_unset_cb
) {
418 return player_tracker_
.RegisterPlayer(new_key_cb
, cdm_unset_cb
);
421 void MediaDrmBridge::UnregisterPlayer(int registration_id
) {
422 player_tracker_
.UnregisterPlayer(registration_id
);
425 void MediaDrmBridge::SetMediaCryptoReadyCB(const base::Closure
& closure
) {
426 if (closure
.is_null()) {
427 media_crypto_ready_cb_
.Reset();
431 DCHECK(media_crypto_ready_cb_
.is_null());
433 if (!GetMediaCrypto().is_null()) {
434 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
, closure
);
438 media_crypto_ready_cb_
= closure
;
441 void MediaDrmBridge::OnMediaCryptoReady(JNIEnv
* env
, jobject
) {
442 DCHECK(!GetMediaCrypto().is_null());
443 if (!media_crypto_ready_cb_
.is_null())
444 base::ResetAndReturn(&media_crypto_ready_cb_
).Run();
447 void MediaDrmBridge::OnPromiseResolved(JNIEnv
* env
,
450 cdm_promise_adapter_
.ResolvePromise(j_promise_id
);
453 void MediaDrmBridge::OnPromiseResolvedWithSession(JNIEnv
* env
,
456 jbyteArray j_session_id
) {
457 cdm_promise_adapter_
.ResolvePromise(j_promise_id
,
458 GetSessionId(env
, j_session_id
));
461 void MediaDrmBridge::OnPromiseRejected(JNIEnv
* env
,
464 jstring j_error_message
) {
465 std::string error_message
= ConvertJavaStringToUTF8(env
, j_error_message
);
466 cdm_promise_adapter_
.RejectPromise(j_promise_id
, MediaKeys::UNKNOWN_ERROR
, 0,
470 void MediaDrmBridge::OnSessionMessage(JNIEnv
* env
,
472 jbyteArray j_session_id
,
473 jbyteArray j_message
,
474 jstring j_legacy_destination_url
) {
475 std::vector
<uint8
> message
;
476 JavaByteArrayToByteVector(env
, j_message
, &message
);
477 GURL legacy_destination_url
=
478 GURL(ConvertJavaStringToUTF8(env
, j_legacy_destination_url
));
479 // Note: Message type is not supported in MediaDrm. Do our best guess here.
480 media::MediaKeys::MessageType message_type
=
481 legacy_destination_url
.is_empty() ? media::MediaKeys::LICENSE_REQUEST
482 : media::MediaKeys::LICENSE_RENEWAL
;
484 session_message_cb_
.Run(GetSessionId(env
, j_session_id
), message_type
,
485 message
, legacy_destination_url
);
488 void MediaDrmBridge::OnSessionClosed(JNIEnv
* env
,
490 jbyteArray j_session_id
) {
491 session_closed_cb_
.Run(GetSessionId(env
, j_session_id
));
494 void MediaDrmBridge::OnSessionKeysChange(JNIEnv
* env
,
496 jbyteArray j_session_id
,
497 bool has_additional_usable_key
,
499 if (has_additional_usable_key
)
500 player_tracker_
.NotifyNewKey();
502 scoped_ptr
<CdmKeyInformation
> cdm_key_information(new CdmKeyInformation());
503 cdm_key_information
->key_id
.assign(kDummyKeyId
,
504 kDummyKeyId
+ sizeof(kDummyKeyId
));
505 cdm_key_information
->status
=
506 static_cast<CdmKeyInformation::KeyStatus
>(j_key_status
);
507 CdmKeysInfo cdm_keys_info
;
508 cdm_keys_info
.push_back(cdm_key_information
.release());
510 session_keys_change_cb_
.Run(GetSessionId(env
, j_session_id
),
511 has_additional_usable_key
, cdm_keys_info
.Pass());
514 void MediaDrmBridge::OnLegacySessionError(JNIEnv
* env
,
516 jbyteArray j_session_id
,
517 jstring j_error_message
) {
518 std::string error_message
= ConvertJavaStringToUTF8(env
, j_error_message
);
519 legacy_session_error_cb_
.Run(GetSessionId(env
, j_session_id
),
520 MediaKeys::UNKNOWN_ERROR
, 0, error_message
);
523 ScopedJavaLocalRef
<jobject
> MediaDrmBridge::GetMediaCrypto() {
524 JNIEnv
* env
= AttachCurrentThread();
525 return Java_MediaDrmBridge_getMediaCrypto(env
, j_media_drm_
.obj());
528 MediaDrmBridge::SecurityLevel
MediaDrmBridge::GetSecurityLevel() {
529 JNIEnv
* env
= AttachCurrentThread();
530 ScopedJavaLocalRef
<jstring
> j_security_level
=
531 Java_MediaDrmBridge_getSecurityLevel(env
, j_media_drm_
.obj());
532 std::string security_level_str
=
533 ConvertJavaStringToUTF8(env
, j_security_level
.obj());
534 return GetSecurityLevelFromString(security_level_str
);
537 bool MediaDrmBridge::IsProtectedSurfaceRequired() {
538 // For Widevine, this depends on the security level.
539 if (std::equal(scheme_uuid_
.begin(), scheme_uuid_
.end(), kWidevineUuid
))
540 return IsSecureDecoderRequired(GetSecurityLevel());
542 // For other key systems, assume true.
546 void MediaDrmBridge::ResetDeviceCredentials(
547 const ResetCredentialsCB
& callback
) {
548 DCHECK(reset_credentials_cb_
.is_null());
549 reset_credentials_cb_
= callback
;
550 JNIEnv
* env
= AttachCurrentThread();
551 Java_MediaDrmBridge_resetDeviceCredentials(env
, j_media_drm_
.obj());
554 void MediaDrmBridge::OnResetDeviceCredentialsCompleted(
555 JNIEnv
* env
, jobject
, bool success
) {
556 base::ResetAndReturn(&reset_credentials_cb_
).Run(success
);