Bug 1306196-[P2] Provide RemoteMediaDrmBridge & corresponding stub to underlying GeckoMediaDrmBridge implementation.
MozReview-Commit-ID: K49zWmuJOpt
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaManager.java
@@ -20,17 +20,17 @@ public final class MediaManager extends
public ICodec createCodec() throws RemoteException {
return new Codec();
}
@Override
public IMediaDrmBridge createRemoteMediaDrmBridge(String keySystem,
String stubId)
throws RemoteException {
- return null;
+ return new RemoteMediaDrmBridgeStub(keySystem, stubId);
}
};
@Override
public synchronized void onCreate() {
if (!sNativeLibLoaded) {
GeckoLoader.doLoadLibrary(this, "mozglue");
sNativeLibLoaded = true;
--- a/mobile/android/base/java/org/mozilla/gecko/media/RemoteManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/RemoteManager.java
@@ -139,16 +139,33 @@ public final class RemoteManager impleme
return null;
}
}
private static final String MEDIA_DECODING_PROCESS_CRASH = "MEDIA_DECODING_PROCESS_CRASH";
private void reportDecodingProcessCrash() {
Telemetry.addToHistogram(MEDIA_DECODING_PROCESS_CRASH, 1);
}
+
+ public synchronized IMediaDrmBridge createRemoteMediaDrmBridge(String keySystem,
+ String stubId) {
+ if (mRemote == null) {
+ if (DEBUG) Log.d(LOGTAG, "createRemoteMediaDrmBridge failed due to not initialize");
+ return null;
+ }
+ try {
+ IMediaDrmBridge remoteBridge =
+ mRemote.createRemoteMediaDrmBridge(keySystem, stubId);
+ return remoteBridge;
+ } catch (RemoteException e) {
+ Log.e(LOGTAG, "Got exception during createRemoteMediaDrmBridge().", e);
+ return null;
+ }
+ }
+
@Override
public void binderDied() {
Log.e(LOGTAG, "remote codec is dead");
reportDecodingProcessCrash();
handleRemoteDeath();
}
private synchronized void handleRemoteDeath() {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridge.java
@@ -0,0 +1,152 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.media;
+
+import android.media.MediaCrypto;
+import android.util.Log;
+
+final class RemoteMediaDrmBridge implements GeckoMediaDrm {
+ private static final String LOGTAG = "GeckoRemoteMediaDrmBridge";
+ private static final boolean DEBUG = false;
+ private CallbacksForwarder mCallbacksFwd;
+ private IMediaDrmBridge mRemote;
+
+ // Forward callbacks from remote bridge stub to MediaDrmProxy.
+ private static class CallbacksForwarder extends IMediaDrmBridgeCallbacks.Stub {
+ private final GeckoMediaDrm.Callbacks mProxyCallbacks;
+ CallbacksForwarder(Callbacks callbacks) {
+ assertTrue(callbacks != null);
+ mProxyCallbacks = callbacks;
+ }
+
+ @Override
+ public void onSessionCreated(int createSessionToken,
+ int promiseId,
+ byte[] sessionId,
+ byte[] request) {
+ mProxyCallbacks.onSessionCreated(createSessionToken,
+ promiseId,
+ sessionId,
+ request);
+ }
+
+ @Override
+ public void onSessionUpdated(int promiseId, byte[] sessionId) {
+ mProxyCallbacks.onSessionUpdated(promiseId, sessionId);
+ }
+
+ @Override
+ public void onSessionClosed(int promiseId, byte[] sessionId) {
+ mProxyCallbacks.onSessionClosed(promiseId, sessionId);
+ }
+
+ @Override
+ public void onSessionMessage(byte[] sessionId,
+ int sessionMessageType,
+ byte[] request) {
+ mProxyCallbacks.onSessionMessage(sessionId, sessionMessageType, request);
+ }
+
+ @Override
+ public void onSessionError(byte[] sessionId, String message) {
+ mProxyCallbacks.onSessionError(sessionId, message);
+ }
+
+ @Override
+ public void onSessionBatchedKeyChanged(byte[] sessionId,
+ SessionKeyInfo[] keyInfos) {
+ mProxyCallbacks.onSessionBatchedKeyChanged(sessionId, keyInfos);
+ }
+
+ @Override
+ public void onRejectPromise(int promiseId, String message) {
+ mProxyCallbacks.onRejectPromise(promiseId, message);
+ }
+ } // CallbacksForwarder
+
+ /* package-private */ static void assertTrue(boolean condition) {
+ if (DEBUG && !condition) {
+ throw new AssertionError("Expected condition to be true");
+ }
+ }
+
+ public RemoteMediaDrmBridge(IMediaDrmBridge remoteBridge) {
+ assertTrue(remoteBridge != null);
+ mRemote = remoteBridge;
+ }
+
+ @Override
+ public synchronized void setCallbacks(Callbacks callbacks) {
+ if (DEBUG) Log.d(LOGTAG, "setCallbacks()");
+ assertTrue(callbacks != null);
+ assertTrue(mRemote != null);
+
+ mCallbacksFwd = new CallbacksForwarder(callbacks);
+ try {
+ mRemote.setCallbacks(mCallbacksFwd);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Got exception during setCallbacks", e);
+ }
+ }
+
+ @Override
+ public synchronized void createSession(int createSessionToken,
+ int promiseId,
+ String initDataType,
+ byte[] initData) {
+ if (DEBUG) Log.d(LOGTAG, "createSession()");
+
+ try {
+ mRemote.createSession(createSessionToken, promiseId, initDataType, initData);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Got exception while creating remote session.", e);
+ mCallbacksFwd.onRejectPromise(promiseId, "Failed to create session.");
+ }
+ }
+
+ @Override
+ public synchronized void updateSession(int promiseId, String sessionId, byte[] response) {
+ if (DEBUG) Log.d(LOGTAG, "updateSession()");
+
+ try {
+ mRemote.updateSession(promiseId, sessionId, response);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Got exception while updating remote session.", e);
+ mCallbacksFwd.onRejectPromise(promiseId, "Failed to update session.");
+ }
+ }
+
+ @Override
+ public synchronized void closeSession(int promiseId, String sessionId) {
+ if (DEBUG) Log.d(LOGTAG, "closeSession()");
+
+ try {
+ mRemote.closeSession(promiseId, sessionId);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Got exception while closing remote session.", e);
+ mCallbacksFwd.onRejectPromise(promiseId, "Failed to close session.");
+ }
+ }
+
+ @Override
+ public synchronized void release() {
+ if (DEBUG) Log.d(LOGTAG, "release()");
+
+ try {
+ mRemote.release();
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Got exception while releasing RemoteDrmBridge.", e);
+ }
+ mRemote = null;
+ mCallbacksFwd = null;
+ }
+
+ @Override
+ public synchronized MediaCrypto getMediaCrypto() {
+ if (DEBUG) Log.d(LOGTAG, "getMediaCrypto(), should not enter here!");
+ assertTrue(false);
+ return null;
+ }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridgeStub.java
@@ -0,0 +1,247 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.media;
+import org.mozilla.gecko.AppConstants;
+
+import java.util.ArrayList;
+
+import android.media.MediaCrypto;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+final class RemoteMediaDrmBridgeStub extends IMediaDrmBridge.Stub implements IBinder.DeathRecipient {
+ private static final String LOGTAG = "GeckoRemoteMediaDrmBridgeStub";
+ private static final boolean DEBUG = false;
+ private volatile IMediaDrmBridgeCallbacks mCallbacks = null;
+
+ // Underlying bridge implmenetaion, i.e. GeckoMediaDrmBrdigeV21.
+ private GeckoMediaDrm mBridge = null;
+
+ // mStubId is initialized during stub construction. It should be a unique
+ // string which is generated in MediaDrmProxy in Fennec App process and is
+ // used for Codec to obtain corresponding MediaCrypto as input to achieve
+ // decryption.
+ // The generated stubId will be delivered to Codec via a code path starting
+ // from MediaDrmProxy -> MediaDrmCDMProxy -> RemoteDataDecoder => IPC => Codec.
+ private String mStubId = "";
+
+ public static ArrayList<RemoteMediaDrmBridgeStub> mBridgeStubs =
+ new ArrayList<RemoteMediaDrmBridgeStub>();
+
+ private String getId() {
+ return mStubId;
+ }
+
+ private MediaCrypto getMediaCryptoFromBridge() {
+ return mBridge != null ? mBridge.getMediaCrypto() : null;
+ }
+
+ public static synchronized MediaCrypto getMediaCrypto(String stubId) {
+ if (DEBUG) Log.d(LOGTAG, "getMediaCrypto()");
+
+ for (int i = 0; i < mBridgeStubs.size(); i++) {
+ if (mBridgeStubs.get(i) != null &&
+ mBridgeStubs.get(i).getId().equals(stubId)) {
+ return mBridgeStubs.get(i).getMediaCryptoFromBridge();
+ }
+ }
+ return null;
+ }
+
+ // Callback to RemoteMediaDrmBridge.
+ private final class Callbacks implements GeckoMediaDrm.Callbacks {
+ private IMediaDrmBridgeCallbacks mRemoteCallbacks;
+
+ public Callbacks(IMediaDrmBridgeCallbacks remote) {
+ mRemoteCallbacks = remote;
+ }
+
+ @Override
+ public void onSessionCreated(int createSessionToken,
+ int promiseId,
+ byte[] sessionId,
+ byte[] request) {
+ if (DEBUG) Log.d(LOGTAG, "onSessionCreated()");
+ try {
+ mRemoteCallbacks.onSessionCreated(createSessionToken,
+ promiseId,
+ sessionId,
+ request);
+ } catch (RemoteException e) {
+ Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
+ }
+ }
+
+ @Override
+ public void onSessionUpdated(int promiseId, byte[] sessionId) {
+ if (DEBUG) Log.d(LOGTAG, "onSessionUpdated()");
+ try {
+ mRemoteCallbacks.onSessionUpdated(promiseId, sessionId);
+ } catch (RemoteException e) {
+ Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
+ }
+ }
+
+ @Override
+ public void onSessionClosed(int promiseId, byte[] sessionId) {
+ if (DEBUG) Log.d(LOGTAG, "onSessionClosed()");
+ try {
+ mRemoteCallbacks.onSessionClosed(promiseId, sessionId);
+ } catch (RemoteException e) {
+ Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
+ }
+ }
+
+ @Override
+ public void onSessionMessage(byte[] sessionId,
+ int sessionMessageType,
+ byte[] request) {
+ if (DEBUG) Log.d(LOGTAG, "onSessionMessage()");
+ try {
+ mRemoteCallbacks.onSessionMessage(sessionId, sessionMessageType, request);
+ } catch (RemoteException e) {
+ Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
+ }
+ }
+
+ @Override
+ public void onSessionError(byte[] sessionId, String message) {
+ if (DEBUG) Log.d(LOGTAG, "onSessionError()");
+ try {
+ mRemoteCallbacks.onSessionError(sessionId, message);
+ } catch (RemoteException e) {
+ Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
+ }
+ }
+
+ @Override
+ public void onSessionBatchedKeyChanged(byte[] sessionId,
+ SessionKeyInfo[] keyInfos) {
+ if (DEBUG) Log.d(LOGTAG, "onSessionBatchedKeyChanged()");
+ try {
+ mRemoteCallbacks.onSessionBatchedKeyChanged(sessionId, keyInfos);
+ } catch (RemoteException e) {
+ Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
+ }
+ }
+
+ @Override
+ public void onRejectPromise(int promiseId, String message) {
+ if (DEBUG) Log.d(LOGTAG, "onRejectPromise()");
+ try {
+ mRemoteCallbacks.onRejectPromise(promiseId, message);
+ } catch (RemoteException e) {
+ Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
+ }
+ }
+ }
+
+ /* package-private */ void assertTrue(boolean condition) {
+ if (DEBUG && !condition) {
+ throw new AssertionError("Expected condition to be true");
+ }
+ }
+
+ RemoteMediaDrmBridgeStub(String keySystem, String stubId) throws RemoteException {
+ if (AppConstants.Versions.preLollipop) {
+ Log.e(LOGTAG, "Pre-Lollipop should never enter here!!");
+ throw new RemoteException("Error, unsupported version!");
+ }
+ try {
+ if (AppConstants.Versions.feature21Plus &&
+ AppConstants.Versions.preMarshmallow) {
+ mBridge = new GeckoMediaDrmBridgeV21(keySystem);
+ } else {
+ mBridge = new GeckoMediaDrmBridgeV23(keySystem);
+ }
+ mStubId = stubId;
+ mBridgeStubs.add(this);
+ } catch (Exception e) {
+ throw new RemoteException("RemoteMediaDrmBridgeStub cannot create bridge implementation.");
+ }
+ }
+
+ @Override
+ public synchronized void setCallbacks(IMediaDrmBridgeCallbacks callbacks) throws RemoteException {
+ if (DEBUG) Log.d(LOGTAG, "setCallbacks()");
+ assertTrue(mBridge != null);
+ assertTrue(callbacks != null);
+ mCallbacks = callbacks;
+ callbacks.asBinder().linkToDeath(this, 0);
+ mBridge.setCallbacks(new Callbacks(mCallbacks));
+ }
+
+ @Override
+ public synchronized void createSession(int createSessionToken,
+ int promiseId,
+ String initDataType,
+ byte[] initData) throws RemoteException {
+ if (DEBUG) Log.d(LOGTAG, "createSession()");
+ try {
+ assertTrue(mCallbacks != null);
+ assertTrue(mBridge != null);
+ mBridge.createSession(createSessionToken,
+ promiseId,
+ initDataType,
+ initData);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Failed to createSession.", e);
+ mCallbacks.onRejectPromise(promiseId, "Failed to createSession.");
+ }
+ }
+
+ @Override
+ public synchronized void updateSession(int promiseId,
+ String sessionId,
+ byte[] response) throws RemoteException {
+ if (DEBUG) Log.d(LOGTAG, "updateSession()");
+ try {
+ assertTrue(mCallbacks != null);
+ assertTrue(mBridge != null);
+ mBridge.updateSession(promiseId, sessionId, response);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Failed to updateSession.", e);
+ mCallbacks.onRejectPromise(promiseId, "Failed to updateSession.");
+ }
+ }
+
+ @Override
+ public synchronized void closeSession(int promiseId, String sessionId) throws RemoteException {
+ if (DEBUG) Log.d(LOGTAG, "closeSession()");
+ try {
+ assertTrue(mCallbacks != null);
+ assertTrue(mBridge != null);
+ mBridge.closeSession(promiseId, sessionId);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Failed to closeSession.", e);
+ mCallbacks.onRejectPromise(promiseId, "Failed to closeSession.");
+ }
+ }
+
+ // IBinder.DeathRecipient
+ @Override
+ public synchronized void binderDied() {
+ Log.e(LOGTAG, "Binder died !!");
+ try {
+ release();
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
+ }
+ }
+
+ @Override
+ public synchronized void release() {
+ if (DEBUG) Log.d(LOGTAG, "release()");
+ mBridgeStubs.remove(this);
+ if (mBridge != null) {
+ mBridge.release();
+ mBridge = null;
+ }
+ mCallbacks.asBinder().unlinkToDeath(this, 0);
+ mCallbacks = null;
+ mStubId = "";
+ }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -559,16 +559,18 @@ gbjar.sources += ['java/org/mozilla/geck
'media/GeckoMediaDrmBridgeV21.java',
'media/GeckoMediaDrmBridgeV23.java',
'media/JellyBeanAsyncCodec.java',
'media/LocalMediaDrmBridge.java',
'media/MediaControlService.java',
'media/MediaDrmProxy.java',
'media/MediaManager.java',
'media/RemoteManager.java',
+ 'media/RemoteMediaDrmBridge.java',
+ 'media/RemoteMediaDrmBridgeStub.java',
'media/Sample.java',
'media/SamplePool.java',
'media/SessionKeyInfo.java',
'media/VideoPlayer.java',
'MediaCastingBar.java',
'MemoryMonitor.java',
'menu/GeckoMenu.java',
'menu/GeckoMenuInflater.java',