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..348debc92f4d1249332a62c78a2cf86beb0f8d89 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 { |
+ 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); |
+ boolean isKeyRelease = sessionInfo.keyType() == MediaDrm.KEY_TYPE_RELEASE; |
- if (keySetId != null && keySetId.length > 0) { |
+ 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 (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); |
- } |
- }); |
+ 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; |
+ } |
+ |
+ 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); |
+ } |
+ } catch (android.media.NotProvisionedException e) { |
+ onPersistentLicenseNoExist(promiseId); |
+ } 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) { |
xhwang
2017/04/05 18:31:50
nit: Please add a comment that this is per Chromiu
yucliu1
2017/04/05 23:04:07
Done.
|
+ 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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { |
- onSessionKeysChange( |
- sessionId, getDummyKeysInfo(MediaDrm.KeyStatus.STATUS_USABLE).toArray(), true); |
+ 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); |
+ } 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) { |
@@ -1120,14 +1285,21 @@ 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); |
+ deferEventHandleIfNeeded(sessionId, new Runnable() { |
+ @Override |
+ public void run() { |
+ Log.d(TAG, |
+ "KeysStatusChange: " + sessionId.toHexString() + ", " |
+ + hasNewUsableKey); |
+ onSessionKeysChange( |
+ sessionId, getKeysInfo(keyInformation).toArray(), hasNewUsableKey); |
+ } |
+ }); |
} |
} |
@@ -1135,13 +1307,50 @@ 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); |
+ } |
} |
} |