Bug 1306196-[P2] Provide RemoteMediaDrmBridge & corresponding stub to underlying GeckoMediaDrmBridge implementation. draft
authorKilik Kuo <kikuo@mozilla.com>
Tue, 01 Nov 2016 23:46:34 +0800
changeset 432745 d8f869412f43dff35104772b52ee39e308b340a0
parent 432744 c70322bf55cbff9c656a577a04eeed5db4d03912
child 535741 98c926cd3d88069d9b0d378ef86529064ed1896d
push id34411
push userkikuo@mozilla.com
push dateWed, 02 Nov 2016 17:40:05 +0000
bugs1306196
milestone52.0a1
Bug 1306196-[P2] Provide RemoteMediaDrmBridge & corresponding stub to underlying GeckoMediaDrmBridge implementation. MozReview-Commit-ID: K49zWmuJOpt
mobile/android/base/java/org/mozilla/gecko/media/MediaManager.java
mobile/android/base/java/org/mozilla/gecko/media/RemoteManager.java
mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridge.java
mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridgeStub.java
mobile/android/base/moz.build
--- 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',