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 f259d4f636d901cd3161b0e6c6c0d701c30c36b0..4f3a343e6da181e743870a46fb58b1c5d9d60930 100644 |
--- a/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java |
+++ b/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java |
@@ -30,11 +30,10 @@ import java.util.UUID; |
public class MediaDrmBridge { |
// Implementation Notes: |
// - A media crypto session (mMediaCryptoSession) is opened after MediaDrm |
- // is created. This session will be added to mSessionIds and will only be |
- // used to create the MediaCrypto object. Its associated mime type is |
- // always null and its session ID is always INVALID_SESSION_ID. |
- // - Each createSession() call creates a new session. All sessions are |
- // managed in mSessionIds. |
+ // is created. This session will NOT be added to mSessionIds and will only |
+ // be used to create the MediaCrypto object. |
+ // - Each createSession() call creates a new session. All created sessions |
+ // are managed in mSessionIds. |
// - Whenever NotProvisionedException is thrown, we will clean up the |
// current state and start the provisioning process. |
// - When provisioning is finished, we will try to resume suspended |
@@ -43,12 +42,10 @@ public class MediaDrmBridge { |
// b) Finish createSession() if previous createSession() was interrupted |
// by a NotProvisionedException. |
// - Whenever an unexpected error occurred, we'll call release() to release |
- // all resources and clear all states. In that case all calls to this |
- // object will be no-op. All public APIs and callbacks should check |
- // mMediaBridge to make sure release() hasn't been called. Also, we call |
- // release() immediately after the error happens (e.g. after mMediaDrm) |
- // calls. Indirect calls should not call release() again to avoid |
- // duplication (even though it doesn't hurt to call release() twice). |
+ // all resources immediately, clear all states and fail all pending |
+ // operations. After that all calls to this object will fail (e.g. return |
+ // null or reject the promise). All public APIs and callbacks should check |
+ // mMediaBridge to make sure release() hasn't been called. |
private static final String TAG = "cr_media"; |
private static final String SECURITY_LEVEL = "securityLevel"; |
@@ -56,7 +53,6 @@ public class MediaDrmBridge { |
private static final String PRIVACY_MODE = "privacyMode"; |
private static final String SESSION_SHARING = "sessionSharing"; |
private static final String ENABLE = "enable"; |
- private static final int INVALID_SESSION_ID = 0; |
private static final char[] HEX_CHAR_LOOKUP = "0123456789ABCDEF".toCharArray(); |
private static final long INVALID_NATIVE_MEDIA_DRM_BRIDGE = 0; |
@@ -69,15 +65,13 @@ public class MediaDrmBridge { |
private long mNativeMediaDrmBridge; |
private UUID mSchemeUUID; |
- // A session only for the purpose of creating a MediaCrypto object. |
- // This session is opened when createSession() is called for the first |
- // time. All following createSession() calls will create a new session and |
- // use it to call getKeyRequest(). No getKeyRequest() should ever be called |
- // on |mMediaCryptoSession|. |
+ // A session only for the purpose of creating a MediaCrypto object. Created |
+ // after construction, or after the provisioning process is successfully |
+ // completed. No getKeyRequest() should be called on |mMediaCryptoSession|. |
private byte[] mMediaCryptoSession; |
- private MediaCrypto mMediaCrypto; |
- // The map of all opened sessions (excluding mMediaCryptoSession) to their mime types. |
+ // The map of all opened sessions (excluding mMediaCryptoSession) to their |
+ // mime types. |
private HashMap<ByteBuffer, String> mSessionIds; |
// The queue of all pending createSession() data. |
@@ -86,10 +80,6 @@ public class MediaDrmBridge { |
private boolean mResetDeviceCredentialsPending; |
// MediaDrmBridge is waiting for provisioning response from the server. |
- // |
- // Notes about NotProvisionedException: This exception can be thrown in a |
- // lot of cases. To streamline implementation, we do not catch it in private |
- // non-native methods and only catch it in public APIs. |
private boolean mProvisioningPending; |
/** |
@@ -213,40 +203,46 @@ public class MediaDrmBridge { |
mMediaDrm.setPropertyString(PRIVACY_MODE, ENABLE); |
mMediaDrm.setPropertyString(SESSION_SHARING, ENABLE); |
- |
- // We could open a MediaCrypto session here to support faster start of |
- // clear lead (no need to wait for createSession()). But on |
- // Android, memory and battery resources are precious and we should |
- // only create a session when we are sure we'll use it. |
- // TODO(xhwang): Investigate other options to support fast start. |
} |
/** |
* Create a MediaCrypto object. |
* |
- * @return whether a MediaCrypto object is successfully created. |
+ * @return false upon fatal error in creating MediaCrypto. Returns true |
+ * otherwise, including the following two cases: |
+ * 1. MediaCrypto is successfully created and notified. |
+ * 2. Device is not provisioned and MediaCrypto creation will be tried |
+ * again after the provisioning process is completed. |
+ * |
+ * When false is returned, the caller should call release(), which will |
+ * notify the native code with a null MediaCrypto, if needed. |
*/ |
- private boolean createMediaCrypto() throws android.media.NotProvisionedException { |
+ private boolean createMediaCrypto() { |
assert mMediaDrm != null; |
assert !mProvisioningPending; |
assert mMediaCryptoSession == null; |
- assert mMediaCrypto == null; |
- // Open media crypto session. This could throw NotProvisionedException. |
- mMediaCryptoSession = openSession(); |
+ // Open media crypto session. |
+ try { |
+ mMediaCryptoSession = openSession(); |
+ } catch (android.media.NotProvisionedException e) { |
+ Log.d(TAG, "Device not provisioned", e); |
+ startProvisioning(); |
+ return true; |
+ } |
+ |
if (mMediaCryptoSession == null) { |
Log.e(TAG, "Cannot create MediaCrypto Session."); |
- release(); |
return false; |
} |
Log.d(TAG, "MediaCrypto Session created: %s", bytesToHexString(mMediaCryptoSession)); |
// Create MediaCrypto object. |
try { |
- // TODO: This requires KitKat. Is this class used on pre-KK devices? |
if (MediaCrypto.isCryptoSchemeSupported(mSchemeUUID)) { |
- mMediaCrypto = new MediaCrypto(mSchemeUUID, mMediaCryptoSession); |
+ MediaCrypto mediaCrypto = new MediaCrypto(mSchemeUUID, mMediaCryptoSession); |
Log.d(TAG, "MediaCrypto successfully created!"); |
+ onMediaCryptoReady(mediaCrypto); |
return true; |
} else { |
Log.e(TAG, "Cannot create MediaCrypto for unsupported scheme."); |
@@ -255,7 +251,9 @@ public class MediaDrmBridge { |
Log.e(TAG, "Cannot create MediaCrypto", e); |
} |
- release(); |
+ mMediaDrm.closeSession(mMediaCryptoSession); |
+ mMediaCryptoSession = null; |
+ |
return false; |
} |
@@ -326,17 +324,20 @@ public class MediaDrmBridge { |
Log.d(TAG, "MediaDrmBridge successfully created."); |
} catch (android.media.UnsupportedSchemeException e) { |
Log.e(TAG, "Unsupported DRM scheme", e); |
+ return null; |
} catch (java.lang.IllegalArgumentException e) { |
Log.e(TAG, "Failed to create MediaDrmBridge", e); |
+ return null; |
} catch (java.lang.IllegalStateException e) { |
Log.e(TAG, "Failed to create MediaDrmBridge", e); |
+ return null; |
} |
- if (mediaDrmBridge == null) { |
+ if (!securityLevel.isEmpty() && !mediaDrmBridge.setSecurityLevel(securityLevel)) { |
return null; |
} |
- if (!securityLevel.isEmpty() && !mediaDrmBridge.setSecurityLevel(securityLevel)) { |
+ if (!mediaDrmBridge.createMediaCrypto()) { |
return null; |
} |
@@ -352,12 +353,9 @@ public class MediaDrmBridge { |
* @return whether the security level was successfully set. |
*/ |
private boolean setSecurityLevel(String securityLevel) { |
+ assert mMediaDrm != null; |
assert !securityLevel.isEmpty(); |
- if (mMediaDrm == null || mMediaCrypto != null) { |
- return false; |
- } |
- |
String currentSecurityLevel = mMediaDrm.getPropertyString(SECURITY_LEVEL); |
Log.e(TAG, "Security level: current %s, new %s", currentSecurityLevel, securityLevel); |
if (securityLevel.equals(currentSecurityLevel)) { |
@@ -405,6 +403,11 @@ public class MediaDrmBridge { |
*/ |
@CalledByNative |
private void resetDeviceCredentials() { |
+ if (mMediaDrm == null) { |
+ onResetDeviceCredentialsCompleted(false); |
+ return; |
+ } |
+ |
mResetDeviceCredentialsPending = true; |
startProvisioning(); |
} |
@@ -426,12 +429,16 @@ public class MediaDrmBridge { |
private void release() { |
// Note that mNativeMediaDrmBridge may have already been reset (see destroy()). |
+ assert mMediaDrm != null; |
+ |
+ // Reject all pending session creation. |
for (PendingCreateSessionData data : mPendingCreateSessionDataQueue) { |
onPromiseRejected(data.promiseId(), "Create session aborted."); |
} |
mPendingCreateSessionDataQueue.clear(); |
mPendingCreateSessionDataQueue = null; |
+ // Close all open sessions. |
for (ByteBuffer sessionId : mSessionIds.keySet()) { |
try { |
// Some implementations don't have removeKeys, crbug/475632 |
@@ -445,12 +452,20 @@ public class MediaDrmBridge { |
mSessionIds.clear(); |
mSessionIds = null; |
- // This session was closed in the "for" loop above. |
- mMediaCryptoSession = null; |
+ // Close mMediaCryptoSession if it's open or notify MediaCrypto |
+ // creation failure if it's never successfully opened. |
+ if (mMediaCryptoSession == null) { |
+ // MediaCrypto never notified. Notify a null one now. |
+ onMediaCryptoReady(null); |
+ } else { |
+ mMediaDrm.closeSession(mMediaCryptoSession); |
+ mMediaCryptoSession = null; |
+ } |
- if (mMediaCrypto != null) { |
- mMediaCrypto.release(); |
- mMediaCrypto = null; |
+ // Fail device credentials resetting. |
+ if (mResetDeviceCredentialsPending) { |
+ mResetDeviceCredentialsPending = false; |
+ onResetDeviceCredentialsCompleted(false); |
} |
if (mMediaDrm != null) { |
@@ -473,7 +488,7 @@ public class MediaDrmBridge { |
byte[] sessionId, byte[] data, String mime, HashMap<String, String> optionalParameters) |
throws android.media.NotProvisionedException { |
assert mMediaDrm != null; |
- assert mMediaCrypto != null; |
+ assert mMediaCryptoSession != null; |
assert !mProvisioningPending; |
if (optionalParameters == null) { |
@@ -511,6 +526,7 @@ public class MediaDrmBridge { |
private void savePendingCreateSessionData(byte[] initData, String mime, |
HashMap<String, String> optionalParameters, long promiseId) { |
Log.d(TAG, "savePendingCreateSessionData()"); |
+ |
mPendingCreateSessionDataQueue.offer( |
new PendingCreateSessionData(initData, mime, optionalParameters, promiseId)); |
} |
@@ -567,34 +583,23 @@ public class MediaDrmBridge { |
private void createSession(byte[] initData, String mime, |
HashMap<String, String> optionalParameters, long promiseId) { |
Log.d(TAG, "createSession()"); |
+ |
if (mMediaDrm == null) { |
Log.e(TAG, "createSession() called when MediaDrm is null."); |
+ onPromiseRejected(promiseId, "MediaDrm released previously."); |
return; |
} |
if (mProvisioningPending) { |
- assert mMediaCrypto == null; |
savePendingCreateSessionData(initData, mime, optionalParameters, promiseId); |
return; |
} |
+ assert mMediaCryptoSession != null; |
+ |
boolean newSessionOpened = false; |
byte[] sessionId = null; |
try { |
- // Create MediaCrypto if necessary. |
- if (mMediaCrypto == null) { |
- boolean success = createMediaCrypto(); |
- // |mMediaCrypto| could be null upon failure. Notify the native code in all cases. |
- onMediaCryptoReady(); |
- if (!success) { |
- onPromiseRejected(promiseId, "MediaCrypto creation failed."); |
- return; |
- } |
- } |
- |
- assert mMediaCryptoSession != null; |
- assert mMediaCrypto != null; |
- |
sessionId = openSession(); |
if (sessionId == null) { |
onPromiseRejected(promiseId, "Open session failed."); |
@@ -763,27 +768,28 @@ public class MediaDrmBridge { |
@CalledByNative |
private void processProvisionResponse(boolean isResponseReceived, byte[] response) { |
Log.d(TAG, "processProvisionResponse()"); |
- assert mProvisioningPending; |
- mProvisioningPending = false; |
// If |mMediaDrm| is released, there is no need to callback native. |
if (mMediaDrm == null) { |
return; |
} |
- boolean success = isResponseReceived; |
- if (success) { |
- success = provideProvisionResponse(response); |
- } |
+ assert mProvisioningPending; |
+ mProvisioningPending = false; |
+ |
+ boolean success = isResponseReceived ? provideProvisionResponse(response) : false; |
if (mResetDeviceCredentialsPending) { |
onResetDeviceCredentialsCompleted(success); |
mResetDeviceCredentialsPending = false; |
} |
- if (success) { |
- processPendingCreateSessionData(); |
+ if (!success || (mMediaCryptoSession == null && !createMediaCrypto())) { |
+ release(); |
+ return; |
} |
+ |
+ processPendingCreateSessionData(); |
} |
/** |
@@ -810,9 +816,9 @@ public class MediaDrmBridge { |
// Helper functions to make native calls. |
- private void onMediaCryptoReady() { |
+ private void onMediaCryptoReady(MediaCrypto mediaCrypto) { |
if (isNativeMediaDrmBridgeValid()) { |
- nativeOnMediaCryptoReady(mNativeMediaDrmBridge, mMediaCrypto); |
+ nativeOnMediaCryptoReady(mNativeMediaDrmBridge, mediaCrypto); |
} |
} |