Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1031)

Unified Diff: media/base/android/java/src/org/chromium/media/MediaDrmBridge.java

Issue 2796843002: [Clank] Load/Remove persistent license (Closed)
Patch Set: Bug fix Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: media/base/android/java/src/org/chromium/media/MediaDrmBridge.java
diff --git a/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java b/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java
index c84061cdc0e57e2d3c67304840cfd5a21b64ed98..a77efeff1233837fc35332f0f81f0731f973a871 100644
--- a/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java
+++ b/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java
@@ -97,6 +97,36 @@ public class MediaDrmBridge {
// Boolean to track if 'ORIGIN' is set in MediaDrm.
private boolean mOriginSet = false;
+ // Delay the MediaDrm event handle if present.
+ private SessionEventDeferrer mSessionEventDeferrer = null;
+
+ // Block MediaDrm event for |mSessionId|.
+ private static class SessionEventDeferrer {
xhwang 2017/04/05 18:31:50 Please add more comments why we need this.
yucliu1 2017/04/05 23:04:07 Done.
+ private final SessionId mSessionId;
+ private final ArrayList<Runnable> mEventHandlers;
+
+ SessionEventDeferrer(SessionId sessionId) {
+ mSessionId = sessionId;
+ mEventHandlers = new ArrayList<>();
+ }
+
+ boolean shouldDefer(SessionId sessionId) {
+ return mSessionId.isEqual(sessionId);
+ }
+
+ void defer(Runnable handler) {
+ mEventHandlers.add(handler);
+ }
+
+ void fire() {
+ for (Runnable r : mEventHandlers) {
+ r.run();
+ }
+
+ mEventHandlers.clear();
+ }
+ }
+
/**
* An equivalent of MediaDrm.KeyStatus, which is only available on M+.
*/
@@ -573,8 +603,10 @@ public class MediaDrmBridge {
MediaDrm.KeyRequest request = null;
try {
- request = mMediaDrm.getKeyRequest(
- sessionId.drmId(), data, mime, keyType, optionalParameters);
+ byte[] scopeId =
+ keyType == MediaDrm.KEY_TYPE_RELEASE ? sessionId.keySetId() : sessionId.drmId();
+ assert scopeId != null;
+ request = mMediaDrm.getKeyRequest(scopeId, data, mime, keyType, optionalParameters);
} catch (IllegalStateException e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && e
instanceof android.media.MediaDrm.MediaDrmStateException) {
@@ -825,19 +857,27 @@ public class MediaDrmBridge {
try {
SessionInfo sessionInfo = mSessionManager.get(sessionId);
- byte[] keySetId = mMediaDrm.provideKeyResponse(sessionId.drmId(), response);
-
- if (keySetId != null && keySetId.length > 0) {
- assert sessionInfo.keyType() == MediaDrm.KEY_TYPE_OFFLINE;
- mSessionManager.setKeySetId(sessionId, keySetId, new Callback<Boolean>() {
- @Override
- public void onResult(Boolean success) {
- onKeyUpdated(sessionId, promiseId, success);
- }
- });
+ boolean isKeyRelease = sessionInfo.keyType() == MediaDrm.KEY_TYPE_RELEASE;
+
+ byte[] keySetId = null;
+ if (isKeyRelease) {
+ Log.d(TAG, "updateSession() for key release");
+ assert sessionId.keySetId() != null;
+ mMediaDrm.provideKeyResponse(sessionId.keySetId(), response);
+ } else {
+ keySetId = mMediaDrm.provideKeyResponse(sessionId.drmId(), response);
+ }
+
+ KeyUpdatedCallback cb = new KeyUpdatedCallback(sessionId, promiseId, isKeyRelease);
+
+ if (isKeyRelease) {
+ mSessionManager.clearPersistentSessionInfo(sessionId, cb);
+ } else if (sessionInfo.keyType() == MediaDrm.KEY_TYPE_OFFLINE && keySetId != null
+ && keySetId.length > 0) {
+ mSessionManager.setKeySetId(sessionId, keySetId, cb);
} else {
// This can be either temporary license update or server certificate update.
- onKeyUpdated(sessionId, promiseId, true);
+ cb.onResult(true);
}
return;
@@ -853,18 +893,130 @@ public class MediaDrmBridge {
release();
}
- private void onKeyUpdated(SessionId sessionId, long promiseId, boolean success) {
- if (!success) {
- onPromiseRejected(promiseId, "failed to update key after response accepted");
+ /**
+ * Load persistent license from stroage.
+ */
+ @CalledByNative
+ private void loadSession(byte[] emeId, final long promiseId) {
+ Log.d(TAG, "loadSession()");
+ if (mProvisioningPending) {
+ onPersistentLicenseNoExist(promiseId);
return;
}
- Log.d(TAG, "Key successfully added for session %s", sessionId.toHexString());
- onPromiseResolved(promiseId);
+ mSessionManager.load(emeId, new Callback<SessionId>() {
+ @Override
+ public void onResult(SessionId sessionId) {
+ if (sessionId == null) {
+ onPersistentLicenseNoExist(promiseId);
+ return;
+ }
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- onSessionKeysChange(
- sessionId, getDummyKeysInfo(MediaDrm.KeyStatus.STATUS_USABLE).toArray(), true);
+ loadSessionWithLoadedStorage(sessionId, promiseId);
+ }
+ });
+ }
+
+ /**
+ * Load session back to memory with MediaDrm. Load persistent storage
+ * before calling this. It will fail if persistent storage isn't loaded.
+ */
+ private void loadSessionWithLoadedStorage(SessionId sessionId, final long promiseId) {
+ byte[] drmId = null;
+ try {
+ drmId = openSession();
+ if (drmId == null) {
+ onPromiseRejected(promiseId, "Failed to open session to load license");
+ return;
+ }
+
+ mSessionManager.setDrmId(sessionId, drmId);
+
+ // Defer event handlers until license is loaded.
+ assert mSessionEventDeferrer == null;
+ mSessionEventDeferrer = new SessionEventDeferrer(sessionId);
+
+ assert sessionId.keySetId() != null;
+ mMediaDrm.restoreKeys(sessionId.drmId(), sessionId.keySetId());
+
+ onPromiseResolvedWithSession(promiseId, sessionId);
+
+ mSessionEventDeferrer.fire();
+ mSessionEventDeferrer = null;
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ onSessionKeysChange(sessionId,
+ getDummyKeysInfo(MediaDrm.KeyStatus.STATUS_USABLE).toArray(), true, false);
+ }
+ } catch (android.media.NotProvisionedException e) {
+ onPersistentLicenseNoExist(promiseId);
xhwang 2017/04/05 18:31:50 When can this happen? When user manually unprovisi
yucliu1 2017/04/05 23:04:07 Early return.
+ } catch (java.lang.IllegalStateException e) {
+ // license doesn't exist
+ if (sessionId.drmId() == null) {
+ // TODO(yucliu): Check if the license is released or doesn't exist.
+ onPersistentLicenseNoExist(promiseId);
+ return;
+ }
+
+ closeSessionNoException(sessionId);
+ mSessionManager.clearPersistentSessionInfo(sessionId, new Callback<Boolean>() {
+ @Override
+ public void onResult(Boolean success) {
+ if (!success) {
+ Log.w(TAG, "Failed to clear persistent storage for non-exist license");
+ }
+
+ onPersistentLicenseNoExist(promiseId);
+ }
+ });
+ }
+ }
+
+ private void onPersistentLicenseNoExist(long promiseId) {
+ onPromiseResolvedWithSession(promiseId, SessionId.createNoExistSessionId());
+ }
+
+ /**
+ * Remove session from device. This will mark the key as released and
+ * generate a key release request. The license is removed from the device
+ * when the session is updated with a license release response.
+ */
+ @CalledByNative
+ private void removeSession(byte[] emeId, long promiseId) {
+ Log.d(TAG, "removeSession()");
+ SessionId sessionId = getSessionIdByEmeId(emeId);
+
+ if (sessionId == null) {
+ onPromiseRejected(promiseId, "Session doesn't exist");
+ return;
+ }
+
+ SessionInfo sessionInfo = mSessionManager.get(sessionId);
+ if (sessionInfo.keyType() != MediaDrm.KEY_TYPE_OFFLINE) {
+ // TODO(yucliu): Support 'remove' of temporary session.
+ onPromiseRejected(promiseId, "Removing temporary session isn't implemented");
+ return;
+ }
+
+ assert sessionId.keySetId() != null;
+
+ mSessionManager.markKeyReleased(sessionId);
+
+ try {
+ // Get key release request.
+ MediaDrm.KeyRequest request = getKeyRequest(
+ sessionId, null, sessionInfo.mimeType(), MediaDrm.KEY_TYPE_RELEASE, null);
+
+ if (request == null) {
+ onPromiseRejected(promiseId, "Fail to generate key release request");
+ return;
+ }
+
+ onPromiseResolved(promiseId);
+ onSessionMessage(sessionId, request);
xhwang 2017/04/05 18:31:50 The spec says queue the message, then return the p
yucliu1 2017/04/05 23:04:07 Switched sequence to match spec.
+ } catch (android.media.NotProvisionedException e) {
+ Log.e(TAG, "removeSession called on unprovisioned device");
+ onPromiseRejected(promiseId, "Unknown failure");
}
}
@@ -969,6 +1121,19 @@ public class MediaDrmBridge {
return false;
}
+ /**
+ * Delay session event handler if |mSessionEventDeferrer| exists and
+ * matches |sessionId|. Otherwise run the handler immediately.
+ */
+ private void deferEventHandleIfNeeded(SessionId sessionId, Runnable handler) {
+ if (mSessionEventDeferrer != null && mSessionEventDeferrer.shouldDefer(sessionId)) {
+ mSessionEventDeferrer.defer(handler);
+ return;
+ }
+
+ handler.run();
+ }
+
// Helper functions to make native calls.
private void onMediaCryptoReady(MediaCrypto mediaCrypto) {
@@ -1022,10 +1187,10 @@ public class MediaDrmBridge {
}
private void onSessionKeysChange(final SessionId sessionId, final Object[] keysInfo,
- final boolean hasAdditionalUsableKey) {
+ final boolean hasAdditionalUsableKey, final boolean isKeyRelease) {
if (isNativeMediaDrmBridgeValid()) {
- nativeOnSessionKeysChange(
- mNativeMediaDrmBridge, sessionId.emeId(), keysInfo, hasAdditionalUsableKey);
+ nativeOnSessionKeysChange(mNativeMediaDrmBridge, sessionId.emeId(), keysInfo,
+ hasAdditionalUsableKey, isKeyRelease);
}
}
@@ -1059,13 +1224,13 @@ public class MediaDrmBridge {
return;
}
+ SessionInfo sessionInfo = mSessionManager.get(sessionId);
switch(event) {
case MediaDrm.EVENT_KEY_REQUIRED:
Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED");
if (mProvisioningPending) {
return;
}
- SessionInfo sessionInfo = mSessionManager.get(sessionId);
MediaDrm.KeyRequest request = null;
try {
request = getKeyRequest(sessionId, data, sessionInfo.mimeType(),
@@ -1082,7 +1247,7 @@ public class MediaDrmBridge {
onSessionKeysChange(sessionId,
getDummyKeysInfo(MediaDrm.KeyStatus.STATUS_INTERNAL_ERROR)
.toArray(),
- false);
+ false, false);
}
Log.e(TAG, "EventListener: getKeyRequest failed.");
return;
@@ -1093,7 +1258,7 @@ public class MediaDrmBridge {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
onSessionKeysChange(sessionId,
getDummyKeysInfo(MediaDrm.KeyStatus.STATUS_EXPIRED).toArray(),
- false);
+ false, sessionInfo.keyType() == MediaDrm.KEY_TYPE_RELEASE);
}
break;
case MediaDrm.EVENT_VENDOR_DEFINED:
@@ -1120,14 +1285,25 @@ public class MediaDrmBridge {
@Override
public void onKeyStatusChange(MediaDrm md, byte[] drmSessionId,
- List<MediaDrm.KeyStatus> keyInformation, boolean hasNewUsableKey) {
- SessionId sessionId = getSessionIdByDrmId(drmSessionId);
+ final List<MediaDrm.KeyStatus> keyInformation, final boolean hasNewUsableKey) {
+ final SessionId sessionId = getSessionIdByDrmId(drmSessionId);
assert sessionId != null;
-
- Log.d(TAG, "KeysStatusChange: " + sessionId.toHexString() + ", " + hasNewUsableKey);
-
- onSessionKeysChange(sessionId, getKeysInfo(keyInformation).toArray(), hasNewUsableKey);
+ assert mSessionManager.get(sessionId) != null;
+
+ final boolean isKeyRelease =
+ mSessionManager.get(sessionId).keyType() == MediaDrm.KEY_TYPE_RELEASE;
+
+ deferEventHandleIfNeeded(sessionId, new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG,
+ "KeysStatusChange: " + sessionId.toHexString() + ", "
+ + hasNewUsableKey);
+ onSessionKeysChange(sessionId, getKeysInfo(keyInformation).toArray(),
+ hasNewUsableKey, isKeyRelease);
+ }
+ });
}
}
@@ -1135,13 +1311,51 @@ public class MediaDrmBridge {
@MainDex
private class ExpirationUpdateListener implements MediaDrm.OnExpirationUpdateListener {
@Override
- public void onExpirationUpdate(MediaDrm md, byte[] drmSessionId, long expirationTime) {
- SessionId sessionId = getSessionIdByDrmId(drmSessionId);
+ public void onExpirationUpdate(
+ MediaDrm md, byte[] drmSessionId, final long expirationTime) {
+ final SessionId sessionId = getSessionIdByDrmId(drmSessionId);
assert sessionId != null;
- Log.d(TAG, "ExpirationUpdate: " + sessionId.toHexString() + ", " + expirationTime);
- onSessionExpirationUpdate(sessionId, expirationTime);
+ deferEventHandleIfNeeded(sessionId, new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG,
+ "ExpirationUpdate: " + sessionId.toHexString() + ", " + expirationTime);
+ onSessionExpirationUpdate(sessionId, expirationTime);
+ }
+ });
+ }
+ }
+
+ @MainDex
+ private class KeyUpdatedCallback extends Callback<Boolean> {
+ private final SessionId mSessionId;
+ private final long mPromiseId;
+ private final boolean mIsKeyRelease;
+
+ KeyUpdatedCallback(SessionId sessionId, long promiseId, boolean isKeyRelease) {
+ mSessionId = sessionId;
+ mPromiseId = promiseId;
+ mIsKeyRelease = isKeyRelease;
+ }
+
+ @Override
+ public void onResult(Boolean success) {
+ if (!success) {
+ onPromiseRejected(mPromiseId, "failed to update key after response accepted");
+ return;
+ }
+
+ Log.d(TAG, "Key successfully %s for session %s", mIsKeyRelease ? "released" : "added",
+ mSessionId.toHexString());
+ onPromiseResolved(mPromiseId);
+
+ if (!mIsKeyRelease && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ onSessionKeysChange(mSessionId,
+ getDummyKeysInfo(MediaDrm.KeyStatus.STATUS_USABLE).toArray(), true,
+ mIsKeyRelease);
+ }
}
}
@@ -1163,7 +1377,7 @@ public class MediaDrmBridge {
long nativeMediaDrmBridge, byte[] emeSessionId, int requestType, byte[] message);
private native void nativeOnSessionClosed(long nativeMediaDrmBridge, byte[] emeSessionId);
private native void nativeOnSessionKeysChange(long nativeMediaDrmBridge, byte[] emeSessionId,
- Object[] keysInfo, boolean hasAdditionalUsableKey);
+ Object[] keysInfo, boolean hasAdditionalUsableKey, boolean isKeyRelease);
private native void nativeOnSessionExpirationUpdate(
long nativeMediaDrmBridge, byte[] emeSessionId, long expirationTime);

Powered by Google App Engine
This is Rietveld 408576698