Chromium Code Reviews| 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); |
| + } |
| } |
| } |