blob: 7e63815c60fbd50631a662f76caecc0527b4da58 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/base/android/media_drm_bridge.h"
#include <algorithm>
#include "base/android/build_info.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/containers/hash_tables.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/sys_byteorder.h"
#include "base/sys_info.h"
#include "base/thread_task_runner_handle.h"
#include "jni/MediaDrmBridge_jni.h"
#include "media/base/android/media_client_android.h"
#include "media/base/android/media_drm_bridge_delegate.h"
#include "media/base/android/provision_fetcher.h"
#include "media/base/cdm_key_information.h"
#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR.
using base::android::AttachCurrentThread;
using base::android::ConvertUTF8ToJavaString;
using base::android::ConvertJavaStringToUTF8;
using base::android::JavaByteArrayToByteVector;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
namespace media {
namespace {
// These must be in sync with Android MediaDrm REQUEST_TYPE_XXX constants!
// https://developer.android.com/reference/android/media/MediaDrm.KeyRequest.html
enum class RequestType {
REQUEST_TYPE_INITIAL = 0,
REQUEST_TYPE_RENEWAL = 1,
REQUEST_TYPE_RELEASE = 2,
};
// These must be in sync with Android MediaDrm KEY_STATUS_XXX constants:
// https://developer.android.com/reference/android/media/MediaDrm.KeyStatus.html
enum class KeyStatus {
KEY_STATUS_USABLE = 0,
KEY_STATUS_EXPIRED = 1,
KEY_STATUS_OUTPUT_NOT_ALLOWED = 2,
KEY_STATUS_PENDING = 3,
KEY_STATUS_INTERNAL_ERROR = 4,
};
// Converts jbyteArray (byte[] in Java) into std::string.
std::string AsString(JNIEnv* env, jbyteArray j_byte_array) {
std::vector<uint8_t> byte_vector;
JavaByteArrayToByteVector(env, j_byte_array, &byte_vector);
return std::string(byte_vector.begin(), byte_vector.end());
}
const uint8 kWidevineUuid[16] = {
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, //
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED};
// Convert |init_data_type| to a string supported by MediaDRM.
// "audio"/"video" does not matter, so use "video".
std::string ConvertInitDataType(media::EmeInitDataType init_data_type) {
// TODO(jrummell/xhwang): EME init data types like "webm" and "cenc" are
// supported in API level >=21 for Widevine key system. Switch to use those
// strings when they are officially supported in Android for all key systems.
switch (init_data_type) {
case media::EmeInitDataType::WEBM:
return "video/webm";
case media::EmeInitDataType::CENC:
return "video/mp4";
case media::EmeInitDataType::KEYIDS:
return "keyids";
default:
NOTREACHED();
return "unknown";
}
}
MediaKeys::MessageType GetMessageType(RequestType request_type) {
switch (request_type) {
case RequestType::REQUEST_TYPE_INITIAL:
return MediaKeys::LICENSE_REQUEST;
case RequestType::REQUEST_TYPE_RENEWAL:
return MediaKeys::LICENSE_RENEWAL;
case RequestType::REQUEST_TYPE_RELEASE:
return MediaKeys::LICENSE_RELEASE;
}
NOTREACHED();
return MediaKeys::LICENSE_REQUEST;
}
CdmKeyInformation::KeyStatus ConvertKeyStatus(KeyStatus key_status) {
switch (key_status) {
case KeyStatus::KEY_STATUS_USABLE:
return CdmKeyInformation::USABLE;
case KeyStatus::KEY_STATUS_EXPIRED:
return CdmKeyInformation::EXPIRED;
case KeyStatus::KEY_STATUS_OUTPUT_NOT_ALLOWED:
return CdmKeyInformation::OUTPUT_RESTRICTED;
case KeyStatus::KEY_STATUS_PENDING:
// TODO(xhwang): This should probably be renamed to "PENDING".
return CdmKeyInformation::KEY_STATUS_PENDING;
case KeyStatus::KEY_STATUS_INTERNAL_ERROR:
return CdmKeyInformation::INTERNAL_ERROR;
}
NOTREACHED();
return CdmKeyInformation::INTERNAL_ERROR;
}
class KeySystemManager {
public:
KeySystemManager();
UUID GetUUID(const std::string& key_system);
std::vector<std::string> GetPlatformKeySystemNames();
private:
using KeySystemUuidMap = MediaClientAndroid::KeySystemUuidMap;
KeySystemUuidMap key_system_uuid_map_;
DISALLOW_COPY_AND_ASSIGN(KeySystemManager);
};
KeySystemManager::KeySystemManager() {
// Widevine is always supported in Android.
key_system_uuid_map_[kWidevineKeySystem] =
UUID(kWidevineUuid, kWidevineUuid + arraysize(kWidevineUuid));
MediaClientAndroid* client = GetMediaClientAndroid();
if (client)
client->AddKeySystemUUIDMappings(&key_system_uuid_map_);
}
UUID KeySystemManager::GetUUID(const std::string& key_system) {
KeySystemUuidMap::iterator it = key_system_uuid_map_.find(key_system);
if (it == key_system_uuid_map_.end())
return UUID();
return it->second;
}
std::vector<std::string> KeySystemManager::GetPlatformKeySystemNames() {
std::vector<std::string> key_systems;
for (KeySystemUuidMap::iterator it = key_system_uuid_map_.begin();
it != key_system_uuid_map_.end(); ++it) {
// Rule out the key system handled by Chrome explicitly.
if (it->first != kWidevineKeySystem)
key_systems.push_back(it->first);
}
return key_systems;
}
base::LazyInstance<KeySystemManager>::Leaky g_key_system_manager =
LAZY_INSTANCE_INITIALIZER;
// Checks whether |key_system| is supported with |container_mime_type|. Only
// checks |key_system| support if |container_mime_type| is empty.
// TODO(xhwang): The |container_mime_type| is not the same as contentType in
// the EME spec. Revisit this once the spec issue with initData type is
// resolved.
bool IsKeySystemSupportedWithTypeImpl(const std::string& key_system,
const std::string& container_mime_type) {
if (!MediaDrmBridge::IsAvailable())
return false;
UUID scheme_uuid = g_key_system_manager.Get().GetUUID(key_system);
if (scheme_uuid.empty())
return false;
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jbyteArray> j_scheme_uuid =
base::android::ToJavaByteArray(env, &scheme_uuid[0], scheme_uuid.size());
ScopedJavaLocalRef<jstring> j_container_mime_type =
ConvertUTF8ToJavaString(env, container_mime_type);
return Java_MediaDrmBridge_isCryptoSchemeSupported(
env, j_scheme_uuid.obj(), j_container_mime_type.obj());
}
MediaDrmBridge::SecurityLevel GetSecurityLevelFromString(
const std::string& security_level_str) {
if (0 == security_level_str.compare("L1"))
return MediaDrmBridge::SECURITY_LEVEL_1;
if (0 == security_level_str.compare("L3"))
return MediaDrmBridge::SECURITY_LEVEL_3;
DCHECK(security_level_str.empty());
return MediaDrmBridge::SECURITY_LEVEL_NONE;
}
std::string GetSecurityLevelString(
MediaDrmBridge::SecurityLevel security_level) {
switch (security_level) {
case MediaDrmBridge::SECURITY_LEVEL_NONE:
return "";
case MediaDrmBridge::SECURITY_LEVEL_1:
return "L1";
case MediaDrmBridge::SECURITY_LEVEL_3:
return "L3";
}
return "";
}
} // namespace
// static
bool MediaDrmBridge::IsAvailable() {
if (base::android::BuildInfo::GetInstance()->sdk_int() < 19)
return false;
int32 os_major_version = 0;
int32 os_minor_version = 0;
int32 os_bugfix_version = 0;
base::SysInfo::OperatingSystemVersionNumbers(
&os_major_version, &os_minor_version, &os_bugfix_version);
if (os_major_version == 4 && os_minor_version == 4 && os_bugfix_version == 0)
return false;
return true;
}
// static
bool MediaDrmBridge::RegisterMediaDrmBridge(JNIEnv* env) {
return RegisterNativesImpl(env);
}
// static
bool MediaDrmBridge::IsKeySystemSupported(const std::string& key_system) {
DCHECK(!key_system.empty());
return IsKeySystemSupportedWithTypeImpl(key_system, "");
}
// static
bool MediaDrmBridge::IsKeySystemSupportedWithType(
const std::string& key_system,
const std::string& container_mime_type) {
DCHECK(!key_system.empty() && !container_mime_type.empty());
return IsKeySystemSupportedWithTypeImpl(key_system, container_mime_type);
}
// static
std::vector<std::string> MediaDrmBridge::GetPlatformKeySystemNames() {
return g_key_system_manager.Get().GetPlatformKeySystemNames();
}
// static
scoped_refptr<MediaDrmBridge> MediaDrmBridge::Create(
const std::string& key_system,
const CreateFetcherCB& create_fetcher_cb,
const SessionMessageCB& session_message_cb,
const SessionClosedCB& session_closed_cb,
const LegacySessionErrorCB& legacy_session_error_cb,
const SessionKeysChangeCB& session_keys_change_cb,
const SessionExpirationUpdateCB& session_expiration_update_cb) {
DVLOG(1) << __FUNCTION__;
if (!IsAvailable())
return nullptr;
UUID scheme_uuid = g_key_system_manager.Get().GetUUID(key_system);
if (scheme_uuid.empty())
return nullptr;
scoped_refptr<MediaDrmBridge> media_drm_bridge(
new MediaDrmBridge(scheme_uuid, create_fetcher_cb, session_message_cb,
session_closed_cb, legacy_session_error_cb,
session_keys_change_cb, session_expiration_update_cb));
if (media_drm_bridge->j_media_drm_.is_null())
media_drm_bridge = nullptr;
return media_drm_bridge;
}
// static
scoped_refptr<MediaDrmBridge> MediaDrmBridge::CreateWithoutSessionSupport(
const std::string& key_system,
const CreateFetcherCB& create_fetcher_cb) {
return MediaDrmBridge::Create(key_system, create_fetcher_cb,
SessionMessageCB(), SessionClosedCB(),
LegacySessionErrorCB(), SessionKeysChangeCB(),
SessionExpirationUpdateCB());
}
void MediaDrmBridge::SetServerCertificate(
const std::vector<uint8_t>& certificate,
scoped_ptr<media::SimpleCdmPromise> promise) {
DVLOG(2) << __FUNCTION__;
DCHECK(!certificate.empty());
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jbyteArray> j_certificate;
if (Java_MediaDrmBridge_setServerCertificate(env, j_media_drm_.obj(),
j_certificate.obj())) {
promise->resolve();
} else {
promise->reject(INVALID_ACCESS_ERROR, 0, "Set server certificate failed.");
}
}
void MediaDrmBridge::CreateSessionAndGenerateRequest(
SessionType session_type,
media::EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data,
scoped_ptr<media::NewSessionCdmPromise> promise) {
DVLOG(2) << __FUNCTION__;
if (session_type != media::MediaKeys::TEMPORARY_SESSION) {
NOTIMPLEMENTED() << "EME persistent sessions not yet supported on Android.";
promise->reject(NOT_SUPPORTED_ERROR, 0,
"Only the temporary session type is supported.");
return;
}
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jbyteArray> j_init_data;
ScopedJavaLocalRef<jobjectArray> j_optional_parameters;
MediaClientAndroid* client = GetMediaClientAndroid();
if (client) {
MediaDrmBridgeDelegate* delegate =
client->GetMediaDrmBridgeDelegate(scheme_uuid_);
if (delegate) {
std::vector<uint8> init_data_from_delegate;
std::vector<std::string> optional_parameters_from_delegate;
if (!delegate->OnCreateSession(init_data_type, init_data,
&init_data_from_delegate,
&optional_parameters_from_delegate)) {
promise->reject(INVALID_ACCESS_ERROR, 0, "Invalid init data.");
}
if (!init_data_from_delegate.empty()) {
j_init_data =
base::android::ToJavaByteArray(env, init_data_from_delegate.data(),
init_data_from_delegate.size());
}
if (!optional_parameters_from_delegate.empty()) {
j_optional_parameters = base::android::ToJavaArrayOfStrings(
env, optional_parameters_from_delegate);
}
}
}
if (j_init_data.is_null()) {
j_init_data =
base::android::ToJavaByteArray(env, init_data.data(), init_data.size());
}
ScopedJavaLocalRef<jstring> j_mime =
ConvertUTF8ToJavaString(env, ConvertInitDataType(init_data_type));
uint32_t promise_id = cdm_promise_adapter_.SavePromise(promise.Pass());
Java_MediaDrmBridge_createSessionFromNative(
env, j_media_drm_.obj(), j_init_data.obj(), j_mime.obj(),
j_optional_parameters.obj(), promise_id);
}
void MediaDrmBridge::LoadSession(
SessionType session_type,
const std::string& session_id,
scoped_ptr<media::NewSessionCdmPromise> promise) {
DVLOG(2) << __FUNCTION__;
NOTIMPLEMENTED() << "EME persistent sessions not yet supported on Android.";
promise->reject(NOT_SUPPORTED_ERROR, 0, "LoadSession() is not supported.");
}
void MediaDrmBridge::UpdateSession(
const std::string& session_id,
const std::vector<uint8_t>& response,
scoped_ptr<media::SimpleCdmPromise> promise) {
DVLOG(2) << __FUNCTION__;
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jbyteArray> j_response =
base::android::ToJavaByteArray(env, response.data(), response.size());
ScopedJavaLocalRef<jbyteArray> j_session_id = base::android::ToJavaByteArray(
env, reinterpret_cast<const uint8_t*>(session_id.data()),
session_id.size());
uint32_t promise_id = cdm_promise_adapter_.SavePromise(promise.Pass());
Java_MediaDrmBridge_updateSession(env, j_media_drm_.obj(), j_session_id.obj(),
j_response.obj(), promise_id);
}
void MediaDrmBridge::CloseSession(const std::string& session_id,
scoped_ptr<media::SimpleCdmPromise> promise) {
DVLOG(2) << __FUNCTION__;
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jbyteArray> j_session_id = base::android::ToJavaByteArray(
env, reinterpret_cast<const uint8_t*>(session_id.data()),
session_id.size());
uint32_t promise_id = cdm_promise_adapter_.SavePromise(promise.Pass());
Java_MediaDrmBridge_closeSession(env, j_media_drm_.obj(), j_session_id.obj(),
promise_id);
}
void MediaDrmBridge::RemoveSession(
const std::string& session_id,
scoped_ptr<media::SimpleCdmPromise> promise) {
DVLOG(2) << __FUNCTION__;
NOTIMPLEMENTED() << "EME persistent sessions not yet supported on Android.";
promise->reject(NOT_SUPPORTED_ERROR, 0, "RemoveSession() is not supported.");
}
void MediaDrmBridge::DeleteOnCorrectThread() const {
DVLOG(1) << __FUNCTION__;
if (!task_runner_->BelongsToCurrentThread()) {
// When DeleteSoon returns false, |this| will be leaked, which is okay.
task_runner_->DeleteSoon(FROM_HERE, this);
} else {
delete this;
}
}
int MediaDrmBridge::RegisterPlayer(const base::Closure& new_key_cb,
const base::Closure& cdm_unset_cb) {
// |player_tracker_| can be accessed from any thread.
return player_tracker_.RegisterPlayer(new_key_cb, cdm_unset_cb);
}
void MediaDrmBridge::UnregisterPlayer(int registration_id) {
// |player_tracker_| can be accessed from any thread.
player_tracker_.UnregisterPlayer(registration_id);
}
bool MediaDrmBridge::SetSecurityLevel(SecurityLevel security_level) {
if (security_level != SECURITY_LEVEL_NONE &&
!std::equal(scheme_uuid_.begin(), scheme_uuid_.end(), kWidevineUuid)) {
NOTREACHED() << "Widevine security level " << security_level
<< "used with another key system";
return false;
}
JNIEnv* env = AttachCurrentThread();
std::string security_level_str = GetSecurityLevelString(security_level);
if (security_level_str.empty())
return false;
ScopedJavaLocalRef<jstring> j_security_level =
ConvertUTF8ToJavaString(env, security_level_str);
return Java_MediaDrmBridge_setSecurityLevel(env, j_media_drm_.obj(),
j_security_level.obj());
}
bool MediaDrmBridge::IsProtectedSurfaceRequired() {
// For Widevine, this depends on the security level.
if (std::equal(scheme_uuid_.begin(), scheme_uuid_.end(), kWidevineUuid))
return IsSecureDecoderRequired(GetSecurityLevel());
// For other key systems, assume true.
return true;
}
void MediaDrmBridge::ResetDeviceCredentials(
const ResetCredentialsCB& callback) {
DVLOG(1) << __FUNCTION__;
DCHECK(reset_credentials_cb_.is_null());
reset_credentials_cb_ = callback;
JNIEnv* env = AttachCurrentThread();
Java_MediaDrmBridge_resetDeviceCredentials(env, j_media_drm_.obj());
}
void MediaDrmBridge::ResolvePromise(uint32_t promise_id) {
DVLOG(2) << __FUNCTION__;
cdm_promise_adapter_.ResolvePromise(promise_id);
}
void MediaDrmBridge::ResolvePromiseWithSession(uint32_t promise_id,
const std::string& session_id) {
DVLOG(2) << __FUNCTION__;
cdm_promise_adapter_.ResolvePromise(promise_id, session_id);
}
void MediaDrmBridge::RejectPromise(uint32_t promise_id,
const std::string& error_message) {
DVLOG(2) << __FUNCTION__;
cdm_promise_adapter_.RejectPromise(promise_id, MediaKeys::UNKNOWN_ERROR, 0,
error_message);
}
ScopedJavaLocalRef<jobject> MediaDrmBridge::GetMediaCrypto() {
DCHECK(task_runner_->BelongsToCurrentThread());
JNIEnv* env = AttachCurrentThread();
return Java_MediaDrmBridge_getMediaCrypto(env, j_media_drm_.obj());
}
void MediaDrmBridge::SetMediaCryptoReadyCB(
const MediaCryptoReadyCB& media_crypto_ready_cb) {
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE,
base::Bind(&MediaDrmBridge::SetMediaCryptoReadyCB,
weak_factory_.GetWeakPtr(), media_crypto_ready_cb));
return;
}
DVLOG(1) << __FUNCTION__;
if (media_crypto_ready_cb.is_null()) {
media_crypto_ready_cb_.Reset();
return;
}
DCHECK(media_crypto_ready_cb_.is_null());
// |media_crypto_ready_cb| is already bound to the correct thread
// (either UI or Media).
if (!GetMediaCrypto().is_null()) {
NotifyMediaCryptoReady(media_crypto_ready_cb);
return;
}
media_crypto_ready_cb_ = media_crypto_ready_cb;
}
//------------------------------------------------------------------------------
// The following OnXxx functions are called from Java. The implementation must
// only do minimal work and then post tasks to avoid reentrancy issues.
void MediaDrmBridge::OnMediaCryptoReady(
JNIEnv* env,
const JavaParamRef<jobject>& j_media_drm) {
DCHECK(task_runner_->BelongsToCurrentThread());
DVLOG(1) << __FUNCTION__;
if (media_crypto_ready_cb_.is_null())
return;
task_runner_->PostTask(
FROM_HERE, base::Bind(&MediaDrmBridge::NotifyMediaCryptoReady,
weak_factory_.GetWeakPtr(),
base::ResetAndReturn(&media_crypto_ready_cb_)));
}
void MediaDrmBridge::OnStartProvisioning(
JNIEnv* env,
const JavaParamRef<jobject>& j_media_drm,
const JavaParamRef<jstring>& j_default_url,
const JavaParamRef<jbyteArray>& j_request_data) {
DVLOG(1) << __FUNCTION__;
task_runner_->PostTask(FROM_HERE,
base::Bind(&MediaDrmBridge::SendProvisioningRequest,
weak_factory_.GetWeakPtr(),
ConvertJavaStringToUTF8(env, j_default_url),
AsString(env, j_request_data)));
}
void MediaDrmBridge::OnPromiseResolved(JNIEnv* env,
const JavaParamRef<jobject>& j_media_drm,
jint j_promise_id) {
task_runner_->PostTask(FROM_HERE,
base::Bind(&MediaDrmBridge::ResolvePromise,
weak_factory_.GetWeakPtr(), j_promise_id));
}
void MediaDrmBridge::OnPromiseResolvedWithSession(
JNIEnv* env,
const JavaParamRef<jobject>& j_media_drm,
jint j_promise_id,
const JavaParamRef<jbyteArray>& j_session_id) {
task_runner_->PostTask(FROM_HERE,
base::Bind(&MediaDrmBridge::ResolvePromiseWithSession,
weak_factory_.GetWeakPtr(), j_promise_id,
AsString(env, j_session_id)));
}
void MediaDrmBridge::OnPromiseRejected(
JNIEnv* env,
const JavaParamRef<jobject>& j_media_drm,
jint j_promise_id,
const JavaParamRef<jstring>& j_error_message) {
task_runner_->PostTask(
FROM_HERE,
base::Bind(&MediaDrmBridge::RejectPromise, weak_factory_.GetWeakPtr(),
j_promise_id, ConvertJavaStringToUTF8(env, j_error_message)));
}
void MediaDrmBridge::OnSessionMessage(
JNIEnv* env,
const JavaParamRef<jobject>& j_media_drm,
const JavaParamRef<jbyteArray>& j_session_id,
jint j_message_type,
const JavaParamRef<jbyteArray>& j_message,
const JavaParamRef<jstring>& j_legacy_destination_url) {
DVLOG(2) << __FUNCTION__;
std::vector<uint8> message;
JavaByteArrayToByteVector(env, j_message, &message);
GURL legacy_destination_url =
GURL(ConvertJavaStringToUTF8(env, j_legacy_destination_url));
MediaKeys::MessageType message_type =
GetMessageType(static_cast<RequestType>(j_message_type));
task_runner_->PostTask(
FROM_HERE, base::Bind(session_message_cb_, AsString(env, j_session_id),
message_type, message, legacy_destination_url));
}
void MediaDrmBridge::OnSessionClosed(
JNIEnv* env,
const JavaParamRef<jobject>& j_media_drm,
const JavaParamRef<jbyteArray>& j_session_id) {
DVLOG(2) << __FUNCTION__;
std::string session_id = AsString(env, j_session_id);
task_runner_->PostTask(FROM_HERE, base::Bind(session_closed_cb_, session_id));
}
void MediaDrmBridge::OnSessionKeysChange(
JNIEnv* env,
const JavaParamRef<jobject>& j_media_drm,
const JavaParamRef<jbyteArray>& j_session_id,
const JavaParamRef<jobjectArray>& j_keys_info,
bool has_additional_usable_key) {
DVLOG(2) << __FUNCTION__;
if (has_additional_usable_key)
player_tracker_.NotifyNewKey();
CdmKeysInfo cdm_keys_info;
size_t size = env->GetArrayLength(j_keys_info);
DCHECK_GT(size, 0u);
for (size_t i = 0; i < size; ++i) {
ScopedJavaLocalRef<jobject> j_key_status(
env, env->GetObjectArrayElement(j_keys_info, i));
ScopedJavaLocalRef<jbyteArray> j_key_id =
Java_KeyStatus_getKeyId(env, j_key_status.obj());
std::vector<uint8> key_id;
JavaByteArrayToByteVector(env, j_key_id.obj(), &key_id);
DCHECK(!key_id.empty());
jint j_status_code = Java_KeyStatus_getStatusCode(env, j_key_status.obj());
CdmKeyInformation::KeyStatus key_status =
ConvertKeyStatus(static_cast<KeyStatus>(j_status_code));
DVLOG(2) << __FUNCTION__ << "Key status change: "
<< base::HexEncode(&key_id[0], key_id.size()) << ", "
<< key_status;
cdm_keys_info.push_back(new CdmKeyInformation(key_id, key_status, 0));
}
task_runner_->PostTask(
FROM_HERE,
base::Bind(session_keys_change_cb_, AsString(env, j_session_id),
has_additional_usable_key, base::Passed(&cdm_keys_info)));
}
// According to MeidaDrm documentation [1], zero |expiry_time_ms| means the keys
// will never expire. This will be translated into a NULL base::Time() [2],
// which will then be mapped to a zero Java time [3]. The zero Java time is
// passed to Blink which will then be translated to NaN [4], which is what the
// spec uses to indicate that the license will never expire [5].
// [1]
// http://developer.android.com/reference/android/media/MediaDrm.OnExpirationUpdateListener.html
// [2] See base::Time::FromDoubleT()
// [3] See base::Time::ToJavaTime()
// [4] See MediaKeySession::expirationChanged()
// [5] https://github.com/w3c/encrypted-media/issues/58
void MediaDrmBridge::OnSessionExpirationUpdate(
JNIEnv* env,
const JavaParamRef<jobject>& j_media_drm,
const JavaParamRef<jbyteArray>& j_session_id,
jlong expiry_time_ms) {
DVLOG(2) << __FUNCTION__ << ": " << expiry_time_ms << " ms";
task_runner_->PostTask(
FROM_HERE,
base::Bind(session_expiration_update_cb_, AsString(env, j_session_id),
base::Time::FromDoubleT(expiry_time_ms / 1000.0)));
}
void MediaDrmBridge::OnLegacySessionError(
JNIEnv* env,
const JavaParamRef<jobject>& j_media_drm,
const JavaParamRef<jbyteArray>& j_session_id,
const JavaParamRef<jstring>& j_error_message) {
std::string error_message = ConvertJavaStringToUTF8(env, j_error_message);
DVLOG(2) << __FUNCTION__ << ": " << error_message;
task_runner_->PostTask(
FROM_HERE,
base::Bind(legacy_session_error_cb_, AsString(env, j_session_id),
MediaKeys::UNKNOWN_ERROR, 0, error_message));
}
void MediaDrmBridge::OnResetDeviceCredentialsCompleted(
JNIEnv* env,
const JavaParamRef<jobject>&,
bool success) {
DVLOG(2) << __FUNCTION__ << ": success:" << success;
DCHECK(!reset_credentials_cb_.is_null());
task_runner_->PostTask(
FROM_HERE,
base::Bind(base::ResetAndReturn(&reset_credentials_cb_), success));
}
//------------------------------------------------------------------------------
// The following are private methods.
MediaDrmBridge::MediaDrmBridge(
const std::vector<uint8>& scheme_uuid,
const CreateFetcherCB& create_fetcher_cb,
const SessionMessageCB& session_message_cb,
const SessionClosedCB& session_closed_cb,
const LegacySessionErrorCB& legacy_session_error_cb,
const SessionKeysChangeCB& session_keys_change_cb,
const SessionExpirationUpdateCB& session_expiration_update_cb)
: scheme_uuid_(scheme_uuid),
create_fetcher_cb_(create_fetcher_cb),
session_message_cb_(session_message_cb),
session_closed_cb_(session_closed_cb),
legacy_session_error_cb_(legacy_session_error_cb),
session_keys_change_cb_(session_keys_change_cb),
session_expiration_update_cb_(session_expiration_update_cb),
task_runner_(base::ThreadTaskRunnerHandle::Get()),
weak_factory_(this) {
DVLOG(1) << __FUNCTION__;
DCHECK(!create_fetcher_cb_.is_null());
JNIEnv* env = AttachCurrentThread();
CHECK(env);
ScopedJavaLocalRef<jbyteArray> j_scheme_uuid =
base::android::ToJavaByteArray(env, &scheme_uuid[0], scheme_uuid.size());
j_media_drm_.Reset(Java_MediaDrmBridge_create(
env, j_scheme_uuid.obj(), reinterpret_cast<intptr_t>(this)));
}
MediaDrmBridge::~MediaDrmBridge() {
DCHECK(task_runner_->BelongsToCurrentThread());
DVLOG(1) << __FUNCTION__;
JNIEnv* env = AttachCurrentThread();
// After the call to Java_MediaDrmBridge_destroy() Java won't call native
// methods anymore, this is ensured by MediaDrmBridge.java.
if (!j_media_drm_.is_null())
Java_MediaDrmBridge_destroy(env, j_media_drm_.obj());
player_tracker_.NotifyCdmUnset();
// Rejects all pending promises.
cdm_promise_adapter_.Clear();
}
// TODO(ddorwin): This is specific to Widevine. http://crbug.com/459400
// static
bool MediaDrmBridge::IsSecureDecoderRequired(SecurityLevel security_level) {
DCHECK(IsAvailable());
return SECURITY_LEVEL_1 == security_level;
}
MediaDrmBridge::SecurityLevel MediaDrmBridge::GetSecurityLevel() {
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jstring> j_security_level =
Java_MediaDrmBridge_getSecurityLevel(env, j_media_drm_.obj());
std::string security_level_str =
ConvertJavaStringToUTF8(env, j_security_level.obj());
return GetSecurityLevelFromString(security_level_str);
}
void MediaDrmBridge::NotifyMediaCryptoReady(const MediaCryptoReadyCB& cb) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!cb.is_null());
DCHECK(!GetMediaCrypto().is_null());
// We can use scoped_ptr to pass ScopedJavaGlobalRef with a callback.
scoped_ptr<ScopedJavaGlobalRef<jobject>> j_object_ptr(
new ScopedJavaGlobalRef<jobject>());
j_object_ptr->Reset(AttachCurrentThread(), GetMediaCrypto().obj());
cb.Run(j_object_ptr.Pass(), IsProtectedSurfaceRequired());
}
void MediaDrmBridge::SendProvisioningRequest(const std::string& default_url,
const std::string& request_data) {
DCHECK(task_runner_->BelongsToCurrentThread());
DVLOG(1) << __FUNCTION__;
DCHECK(!provision_fetcher_) << "At most one provision request at any time.";
provision_fetcher_ = create_fetcher_cb_.Run();
provision_fetcher_->Retrieve(
default_url, request_data,
base::Bind(&MediaDrmBridge::ProcessProvisionResponse,
weak_factory_.GetWeakPtr()));
}
void MediaDrmBridge::ProcessProvisionResponse(bool success,
const std::string& response) {
DCHECK(task_runner_->BelongsToCurrentThread());
DVLOG(1) << __FUNCTION__;
DCHECK(provision_fetcher_) << "No provision request pending.";
provision_fetcher_.reset();
if (!success)
VLOG(1) << "Device provision failure: can't get server response";
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jbyteArray> j_response = base::android::ToJavaByteArray(
env, reinterpret_cast<const uint8_t*>(response.data()), response.size());
Java_MediaDrmBridge_processProvisionResponse(env, j_media_drm_.obj(), success,
j_response.obj());
}
} // namespace media