/* 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 mBridgeStubs = new ArrayList(); 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 = ""; } }