Commit ce1d00b6 authored by yucliu's avatar yucliu Committed by Commit bot

[Clank] Load/Remove persistent license

Add support for load/remove persistent license at Android level.

Load:
1. Load eme session ID to key set ID map from persistent storage.
2. MediaDrm.restoreKeys.

Remove:
1. Generate key release request.
2. Update session with key release response.
3. Clear persistent storage.

BUG=493521
TEST=test page

Review-Url: https://codereview.chromium.org/2796843002
Cr-Commit-Position: refs/heads/master@{#462690}
parent 9b7ef119
...@@ -18,6 +18,13 @@ std::string JavaBytesToString(JNIEnv* env, jbyteArray j_byte_array) { ...@@ -18,6 +18,13 @@ std::string JavaBytesToString(JNIEnv* env, jbyteArray j_byte_array) {
return std::string(byte_vector.begin(), byte_vector.end()); return std::string(byte_vector.begin(), byte_vector.end());
} }
base::android::ScopedJavaLocalRef<jbyteArray> StringToJavaBytes(
JNIEnv* env,
const std::string& str) {
return base::android::ToJavaByteArray(
env, reinterpret_cast<const uint8_t*>(str.data()), str.size());
}
JavaObjectPtr CreateJavaObjectPtr(jobject object) { JavaObjectPtr CreateJavaObjectPtr(jobject object) {
JavaObjectPtr j_object_ptr(new base::android::ScopedJavaGlobalRef<jobject>()); JavaObjectPtr j_object_ptr(new base::android::ScopedJavaGlobalRef<jobject>());
j_object_ptr->Reset(base::android::AttachCurrentThread(), object); j_object_ptr->Reset(base::android::AttachCurrentThread(), object);
......
...@@ -15,6 +15,11 @@ using JavaObjectPtr = ...@@ -15,6 +15,11 @@ using JavaObjectPtr =
// Converts jbyteArray (byte[] in Java) into std::string. // Converts jbyteArray (byte[] in Java) into std::string.
std::string JavaBytesToString(JNIEnv* env, jbyteArray j_byte_array); std::string JavaBytesToString(JNIEnv* env, jbyteArray j_byte_array);
// Converts std::string to jbyteArray (byte[] in Java).
base::android::ScopedJavaLocalRef<jbyteArray> StringToJavaBytes(
JNIEnv* env,
const std::string& str);
// A helper method to create a JavaObjectPtr. // A helper method to create a JavaObjectPtr.
JavaObjectPtr CreateJavaObjectPtr(jobject object); JavaObjectPtr CreateJavaObjectPtr(jobject object);
......
...@@ -87,6 +87,13 @@ class MediaDrmSessionManager { ...@@ -87,6 +87,13 @@ class MediaDrmSessionManager {
return new SessionId(drmId, drmId, null /* keySetId */); return new SessionId(drmId, drmId, null /* keySetId */);
} }
/**
* Create session ID used to report session doesn't exist.
*/
static SessionId createNoExistSessionId() {
return createTemporarySessionId(new byte[0]);
}
private SessionId(byte[] emeId, byte[] drmId, byte[] keySetId) { private SessionId(byte[] emeId, byte[] drmId, byte[] keySetId) {
assert emeId != null; assert emeId != null;
assert drmId != null || keySetId != null; assert drmId != null || keySetId != null;
...@@ -165,6 +172,16 @@ class MediaDrmSessionManager { ...@@ -165,6 +172,16 @@ class MediaDrmSessionManager {
return new PersistentInfo(mSessionId.emeId(), mSessionId.keySetId(), mMimeType); return new PersistentInfo(mSessionId.emeId(), mSessionId.keySetId(), mMimeType);
} }
private static SessionInfo fromPersistentInfo(PersistentInfo persistentInfo) {
assert persistentInfo != null;
assert persistentInfo.emeId() != null;
assert persistentInfo.keySetId() != null;
SessionId sessionId = new SessionId(
persistentInfo.emeId(), null /* drmId */, persistentInfo.keySetId());
return new SessionInfo(sessionId, persistentInfo.mimeType(), MediaDrm.KEY_TYPE_OFFLINE);
}
} }
// Maps from DRM/EME session ID to SessionInfo. SessionInfo contains // Maps from DRM/EME session ID to SessionInfo. SessionInfo contains
...@@ -188,6 +205,20 @@ class MediaDrmSessionManager { ...@@ -188,6 +205,20 @@ class MediaDrmSessionManager {
mStorage = storage; mStorage = storage;
} }
/**
* Set drm ID. It should only be called for persistent license session
* without an opened drm session.
*/
void setDrmId(SessionId sessionId, byte[] drmId) {
SessionInfo info = get(sessionId);
assert info != null;
assert info.sessionId().isEqual(sessionId);
sessionId.setDrmId(drmId);
mDrmSessionInfoMap.put(ByteBuffer.wrap(drmId), info);
}
/** /**
* Set key set ID. It should only be called for persistent license session. * Set key set ID. It should only be called for persistent license session.
*/ */
...@@ -201,6 +232,50 @@ class MediaDrmSessionManager { ...@@ -201,6 +232,50 @@ class MediaDrmSessionManager {
mStorage.saveInfo(get(sessionId).toPersistentInfo(), callback); mStorage.saveInfo(get(sessionId).toPersistentInfo(), callback);
} }
/**
* Mark key as released. It should only be called for persistent license
* session.
*/
void markKeyReleased(SessionId sessionId) {
SessionInfo info = get(sessionId);
assert info != null;
assert info.keyType() == MediaDrm.KEY_TYPE_OFFLINE;
info.setKeyType(MediaDrm.KEY_TYPE_RELEASE);
}
/**
* Load |emeId|'s session data from persistent storage.
*/
void load(byte[] emeId, final Callback<SessionId> callback) {
mStorage.loadInfo(emeId, new Callback<PersistentInfo>() {
@Override
public void onResult(PersistentInfo persistentInfo) {
if (persistentInfo == null) {
callback.onResult(null);
return;
}
// Loading same persistent license into different sessions isn't
// supported.
assert getSessionIdByEmeId(persistentInfo.emeId()) == null;
SessionInfo info = SessionInfo.fromPersistentInfo(persistentInfo);
mEmeSessionInfoMap.put(ByteBuffer.wrap(persistentInfo.emeId()), info);
callback.onResult(info.sessionId());
}
});
}
/**
* Remove persistent license info from persistent storage.
*/
void clearPersistentSessionInfo(SessionId sessionId, Callback<Boolean> callback) {
sessionId.setKeySetId(null);
mStorage.clearInfo(sessionId.emeId(), callback);
}
/** /**
* Remove session and related infomration from memory, but doesn't touch * Remove session and related infomration from memory, but doesn't touch
* persistent storage. * persistent storage.
......
...@@ -42,6 +42,18 @@ class MediaDrmStorageBridge { ...@@ -42,6 +42,18 @@ class MediaDrmStorageBridge {
mKeySetId = keySetId; mKeySetId = keySetId;
mMimeType = mime; mMimeType = mime;
} }
byte[] emeId() {
return mEmeId;
}
byte[] keySetId() {
return mKeySetId;
}
String mimeType() {
return mMimeType;
}
} }
MediaDrmStorageBridge(long nativeMediaDrmStorageBridge) { MediaDrmStorageBridge(long nativeMediaDrmStorageBridge) {
......
...@@ -124,12 +124,14 @@ ContentDecryptionModule::MessageType GetMessageType(RequestType request_type) { ...@@ -124,12 +124,14 @@ ContentDecryptionModule::MessageType GetMessageType(RequestType request_type) {
return ContentDecryptionModule::LICENSE_REQUEST; return ContentDecryptionModule::LICENSE_REQUEST;
} }
CdmKeyInformation::KeyStatus ConvertKeyStatus(KeyStatus key_status) { CdmKeyInformation::KeyStatus ConvertKeyStatus(KeyStatus key_status,
bool is_key_release) {
switch (key_status) { switch (key_status) {
case KeyStatus::KEY_STATUS_USABLE: case KeyStatus::KEY_STATUS_USABLE:
return CdmKeyInformation::USABLE; return CdmKeyInformation::USABLE;
case KeyStatus::KEY_STATUS_EXPIRED: case KeyStatus::KEY_STATUS_EXPIRED:
return CdmKeyInformation::EXPIRED; return is_key_release ? CdmKeyInformation::RELEASED
: CdmKeyInformation::EXPIRED;
case KeyStatus::KEY_STATUS_OUTPUT_NOT_ALLOWED: case KeyStatus::KEY_STATUS_OUTPUT_NOT_ALLOWED:
return CdmKeyInformation::OUTPUT_RESTRICTED; return CdmKeyInformation::OUTPUT_RESTRICTED;
case KeyStatus::KEY_STATUS_PENDING: case KeyStatus::KEY_STATUS_PENDING:
...@@ -256,6 +258,13 @@ bool AreMediaDrmApisAvailable() { ...@@ -256,6 +258,13 @@ bool AreMediaDrmApisAvailable() {
return true; return true;
} }
bool IsPersistentLicenseTypeSupportedByMediaDrm() {
return MediaDrmBridge::IsAvailable() &&
// In development. See http://crbug.com/493521
base::FeatureList::IsEnabled(kMediaDrmPersistentLicense) &&
base::android::BuildInfo::GetInstance()->sdk_int() >= 23;
}
} // namespace } // namespace
// MediaDrm is not generally usable without MediaCodec. Thus, both the MediaDrm // MediaDrm is not generally usable without MediaCodec. Thus, both the MediaDrm
...@@ -281,15 +290,9 @@ bool MediaDrmBridge::IsKeySystemSupported(const std::string& key_system) { ...@@ -281,15 +290,9 @@ bool MediaDrmBridge::IsKeySystemSupported(const std::string& key_system) {
// static // static
bool MediaDrmBridge::IsPersistentLicenseTypeSupported( bool MediaDrmBridge::IsPersistentLicenseTypeSupported(
const std::string& key_system) { const std::string& key_system) {
if (!MediaDrmBridge::IsAvailable()) // TODO(yucliu): Check |key_system| if persistent license is supported by
return false; // MediaDrm.
return IsPersistentLicenseTypeSupportedByMediaDrm();
if (!base::FeatureList::IsEnabled(kMediaDrmPersistentLicense)) {
return false;
}
NOTIMPLEMENTED() << "In development. See http://crbug.com/493521";
return false;
} }
// static // static
...@@ -460,11 +463,20 @@ void MediaDrmBridge::LoadSession( ...@@ -460,11 +463,20 @@ void MediaDrmBridge::LoadSession(
DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(task_runner_->BelongsToCurrentThread());
DVLOG(2) << __func__; DVLOG(2) << __func__;
DCHECK(base::FeatureList::IsEnabled(kMediaDrmPersistentLicense)); DCHECK(IsPersistentLicenseTypeSupportedByMediaDrm());
NOTIMPLEMENTED() << "EME persistent sessions not yet supported on Android."; if (session_type != CdmSessionType::PERSISTENT_LICENSE_SESSION) {
promise->reject(CdmPromise::NOT_SUPPORTED_ERROR, 0, promise->reject(
"LoadSession() is not supported."); CdmPromise::NOT_SUPPORTED_ERROR, 0,
"LoadSession() is only supported for 'persistent-license'.");
return;
}
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jbyteArray> j_session_id =
StringToJavaBytes(env, session_id);
uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
Java_MediaDrmBridge_loadSession(env, j_media_drm_, j_session_id, promise_id);
} }
void MediaDrmBridge::UpdateSession( void MediaDrmBridge::UpdateSession(
...@@ -477,9 +489,8 @@ void MediaDrmBridge::UpdateSession( ...@@ -477,9 +489,8 @@ void MediaDrmBridge::UpdateSession(
JNIEnv* env = AttachCurrentThread(); JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jbyteArray> j_response = ScopedJavaLocalRef<jbyteArray> j_response =
base::android::ToJavaByteArray(env, response.data(), response.size()); base::android::ToJavaByteArray(env, response.data(), response.size());
ScopedJavaLocalRef<jbyteArray> j_session_id = base::android::ToJavaByteArray( ScopedJavaLocalRef<jbyteArray> j_session_id =
env, reinterpret_cast<const uint8_t*>(session_id.data()), StringToJavaBytes(env, session_id);
session_id.size());
uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise)); uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
Java_MediaDrmBridge_updateSession(env, j_media_drm_, j_session_id, j_response, Java_MediaDrmBridge_updateSession(env, j_media_drm_, j_session_id, j_response,
promise_id); promise_id);
...@@ -492,9 +503,8 @@ void MediaDrmBridge::CloseSession( ...@@ -492,9 +503,8 @@ void MediaDrmBridge::CloseSession(
DVLOG(2) << __func__; DVLOG(2) << __func__;
JNIEnv* env = AttachCurrentThread(); JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jbyteArray> j_session_id = base::android::ToJavaByteArray( ScopedJavaLocalRef<jbyteArray> j_session_id =
env, reinterpret_cast<const uint8_t*>(session_id.data()), StringToJavaBytes(env, session_id);
session_id.size());
uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise)); uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
Java_MediaDrmBridge_closeSession(env, j_media_drm_, j_session_id, promise_id); Java_MediaDrmBridge_closeSession(env, j_media_drm_, j_session_id, promise_id);
} }
...@@ -505,9 +515,12 @@ void MediaDrmBridge::RemoveSession( ...@@ -505,9 +515,12 @@ void MediaDrmBridge::RemoveSession(
DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(task_runner_->BelongsToCurrentThread());
DVLOG(2) << __func__; DVLOG(2) << __func__;
NOTIMPLEMENTED() << "EME persistent sessions not yet supported on Android."; JNIEnv* env = AttachCurrentThread();
promise->reject(CdmPromise::NOT_SUPPORTED_ERROR, 0, ScopedJavaLocalRef<jbyteArray> j_session_id =
"RemoveSession() is not supported."); StringToJavaBytes(env, session_id);
uint32_t promise_id = cdm_promise_adapter_.SavePromise(std::move(promise));
Java_MediaDrmBridge_removeSession(env, j_media_drm_, j_session_id,
promise_id);
} }
CdmContext* MediaDrmBridge::GetCdmContext() { CdmContext* MediaDrmBridge::GetCdmContext() {
...@@ -698,7 +711,8 @@ void MediaDrmBridge::OnSessionKeysChange( ...@@ -698,7 +711,8 @@ void MediaDrmBridge::OnSessionKeysChange(
const JavaParamRef<jobject>& j_media_drm, const JavaParamRef<jobject>& j_media_drm,
const JavaParamRef<jbyteArray>& j_session_id, const JavaParamRef<jbyteArray>& j_session_id,
const JavaParamRef<jobjectArray>& j_keys_info, const JavaParamRef<jobjectArray>& j_keys_info,
bool has_additional_usable_key) { bool has_additional_usable_key,
bool is_key_release) {
DVLOG(2) << __func__; DVLOG(2) << __func__;
CdmKeysInfo cdm_keys_info; CdmKeysInfo cdm_keys_info;
...@@ -718,7 +732,7 @@ void MediaDrmBridge::OnSessionKeysChange( ...@@ -718,7 +732,7 @@ void MediaDrmBridge::OnSessionKeysChange(
jint j_status_code = Java_KeyStatus_getStatusCode(env, j_key_status); jint j_status_code = Java_KeyStatus_getStatusCode(env, j_key_status);
CdmKeyInformation::KeyStatus key_status = CdmKeyInformation::KeyStatus key_status =
ConvertKeyStatus(static_cast<KeyStatus>(j_status_code)); ConvertKeyStatus(static_cast<KeyStatus>(j_status_code), is_key_release);
DVLOG(2) << __func__ << "Key status change: " DVLOG(2) << __func__ << "Key status change: "
<< base::HexEncode(&key_id[0], key_id.size()) << ", " << base::HexEncode(&key_id[0], key_id.size()) << ", "
...@@ -916,8 +930,7 @@ void MediaDrmBridge::ProcessProvisionResponse(bool success, ...@@ -916,8 +930,7 @@ void MediaDrmBridge::ProcessProvisionResponse(bool success,
JNIEnv* env = AttachCurrentThread(); JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jbyteArray> j_response = base::android::ToJavaByteArray( ScopedJavaLocalRef<jbyteArray> j_response = StringToJavaBytes(env, response);
env, reinterpret_cast<const uint8_t*>(response.data()), response.size());
Java_MediaDrmBridge_processProvisionResponse(env, j_media_drm_, success, Java_MediaDrmBridge_processProvisionResponse(env, j_media_drm_, success,
j_response); j_response);
......
...@@ -214,12 +214,17 @@ class MEDIA_EXPORT MediaDrmBridge : public ContentDecryptionModule, ...@@ -214,12 +214,17 @@ class MEDIA_EXPORT MediaDrmBridge : public ContentDecryptionModule,
const base::android::JavaParamRef<jobject>& j_media_drm, const base::android::JavaParamRef<jobject>& j_media_drm,
const base::android::JavaParamRef<jbyteArray>& j_session_id); const base::android::JavaParamRef<jbyteArray>& j_session_id);
// Called when key statuses of session are changed. |is_key_release| is set to
// true when releasing keys. Some of the MediaDrm key status codes should be
// mapped to CDM key status differently (e.g. EXPIRE -> RELEASED).
void OnSessionKeysChange( void OnSessionKeysChange(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& j_media_drm, const base::android::JavaParamRef<jobject>& j_media_drm,
const base::android::JavaParamRef<jbyteArray>& j_session_id, const base::android::JavaParamRef<jbyteArray>& j_session_id,
// List<KeyStatus>
const base::android::JavaParamRef<jobjectArray>& j_keys_info, const base::android::JavaParamRef<jobjectArray>& j_keys_info,
bool has_additional_usable_key); bool has_additional_usable_key,
bool is_key_release);
// |expiry_time_ms| is the new expiration time for the keys in the session. // |expiry_time_ms| is the new expiration time for the keys in the session.
// The time is in milliseconds, relative to the Unix epoch. A time of 0 // The time is in milliseconds, relative to the Unix epoch. A time of 0
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment