1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 package org.mozilla.gecko.media; 6 7 import java.util.ArrayList; 8 9 import android.media.MediaCrypto; 10 import android.os.Build; 11 import android.os.IBinder; 12 import android.os.RemoteException; 13 import android.util.Log; 14 15 final class RemoteMediaDrmBridgeStub extends IMediaDrmBridge.Stub implements IBinder.DeathRecipient { 16 private static final String LOGTAG = "RemoteDrmBridgeStub"; 17 private static final boolean DEBUG = false; 18 private volatile IMediaDrmBridgeCallbacks mCallbacks = null; 19 20 // Underlying bridge implmenetaion, i.e. GeckoMediaDrmBrdigeV21. 21 private GeckoMediaDrm mBridge = null; 22 23 // mStubId is initialized during stub construction. It should be a unique 24 // string which is generated in MediaDrmProxy in Fennec App process and is 25 // used for Codec to obtain corresponding MediaCrypto as input to achieve 26 // decryption. 27 // The generated stubId will be delivered to Codec via a code path starting 28 // from MediaDrmProxy -> MediaDrmCDMProxy -> RemoteDataDecoder => IPC => Codec. 29 private String mStubId = ""; 30 31 public static final ArrayList<RemoteMediaDrmBridgeStub> mBridgeStubs = 32 new ArrayList<RemoteMediaDrmBridgeStub>(); 33 getId()34 private String getId() { 35 return mStubId; 36 } 37 getMediaCryptoFromBridge()38 private MediaCrypto getMediaCryptoFromBridge() { 39 return mBridge != null ? mBridge.getMediaCrypto() : null; 40 } 41 getMediaCrypto(final String stubId)42 public static synchronized MediaCrypto getMediaCrypto(final String stubId) { 43 if (DEBUG) Log.d(LOGTAG, "getMediaCrypto()"); 44 45 for (int i = 0; i < mBridgeStubs.size(); i++) { 46 if (mBridgeStubs.get(i) != null && 47 mBridgeStubs.get(i).getId().equals(stubId)) { 48 return mBridgeStubs.get(i).getMediaCryptoFromBridge(); 49 } 50 } 51 return null; 52 } 53 54 // Callback to RemoteMediaDrmBridge. 55 private final class Callbacks implements GeckoMediaDrm.Callbacks { 56 private IMediaDrmBridgeCallbacks mRemoteCallbacks; 57 Callbacks(final IMediaDrmBridgeCallbacks remote)58 public Callbacks(final IMediaDrmBridgeCallbacks remote) { 59 mRemoteCallbacks = remote; 60 } 61 62 @Override onSessionCreated(final int createSessionToken, final int promiseId, final byte[] sessionId, final byte[] request)63 public void onSessionCreated(final int createSessionToken, 64 final int promiseId, 65 final byte[] sessionId, 66 final byte[] request) { 67 if (DEBUG) Log.d(LOGTAG, "onSessionCreated()"); 68 try { 69 mRemoteCallbacks.onSessionCreated(createSessionToken, 70 promiseId, 71 sessionId, 72 request); 73 } catch (final RemoteException e) { 74 Log.e(LOGTAG, "Exception ! Dead recipient !!", e); 75 } 76 } 77 78 @Override onSessionUpdated(final int promiseId, final byte[] sessionId)79 public void onSessionUpdated(final int promiseId, final byte[] sessionId) { 80 if (DEBUG) Log.d(LOGTAG, "onSessionUpdated()"); 81 try { 82 mRemoteCallbacks.onSessionUpdated(promiseId, sessionId); 83 } catch (final RemoteException e) { 84 Log.e(LOGTAG, "Exception ! Dead recipient !!", e); 85 } 86 } 87 88 @Override onSessionClosed(final int promiseId, final byte[] sessionId)89 public void onSessionClosed(final int promiseId, final byte[] sessionId) { 90 if (DEBUG) Log.d(LOGTAG, "onSessionClosed()"); 91 try { 92 mRemoteCallbacks.onSessionClosed(promiseId, sessionId); 93 } catch (final RemoteException e) { 94 Log.e(LOGTAG, "Exception ! Dead recipient !!", e); 95 } 96 } 97 98 @Override onSessionMessage(final byte[] sessionId, final int sessionMessageType, final byte[] request)99 public void onSessionMessage(final byte[] sessionId, 100 final int sessionMessageType, 101 final byte[] request) { 102 if (DEBUG) Log.d(LOGTAG, "onSessionMessage()"); 103 try { 104 mRemoteCallbacks.onSessionMessage(sessionId, sessionMessageType, request); 105 } catch (final RemoteException e) { 106 Log.e(LOGTAG, "Exception ! Dead recipient !!", e); 107 } 108 } 109 110 @Override onSessionError(final byte[] sessionId, final String message)111 public void onSessionError(final byte[] sessionId, final String message) { 112 if (DEBUG) Log.d(LOGTAG, "onSessionError()"); 113 try { 114 mRemoteCallbacks.onSessionError(sessionId, message); 115 } catch (final RemoteException e) { 116 Log.e(LOGTAG, "Exception ! Dead recipient !!", e); 117 } 118 } 119 120 @Override onSessionBatchedKeyChanged(final byte[] sessionId, final SessionKeyInfo[] keyInfos)121 public void onSessionBatchedKeyChanged(final byte[] sessionId, 122 final SessionKeyInfo[] keyInfos) { 123 if (DEBUG) Log.d(LOGTAG, "onSessionBatchedKeyChanged()"); 124 try { 125 mRemoteCallbacks.onSessionBatchedKeyChanged(sessionId, keyInfos); 126 } catch (final RemoteException e) { 127 Log.e(LOGTAG, "Exception ! Dead recipient !!", e); 128 } 129 } 130 131 @Override onRejectPromise(final int promiseId, final String message)132 public void onRejectPromise(final int promiseId, final String message) { 133 if (DEBUG) Log.d(LOGTAG, "onRejectPromise()"); 134 try { 135 mRemoteCallbacks.onRejectPromise(promiseId, message); 136 } catch (final RemoteException e) { 137 Log.e(LOGTAG, "Exception ! Dead recipient !!", e); 138 } 139 } 140 } 141 assertTrue(final boolean condition)142 /* package-private */ void assertTrue(final boolean condition) { 143 if (DEBUG && !condition) { 144 throw new AssertionError("Expected condition to be true"); 145 } 146 } 147 RemoteMediaDrmBridgeStub(final String keySystem, final String stubId)148 RemoteMediaDrmBridgeStub(final String keySystem, final String stubId) throws RemoteException { 149 if (Build.VERSION.SDK_INT < 21) { 150 Log.e(LOGTAG, "Pre-Lollipop should never enter here!!"); 151 throw new RemoteException("Error, unsupported version!"); 152 } 153 try { 154 if (Build.VERSION.SDK_INT < 23) { 155 mBridge = new GeckoMediaDrmBridgeV21(keySystem); 156 } else { 157 mBridge = new GeckoMediaDrmBridgeV23(keySystem); 158 } 159 mStubId = stubId; 160 mBridgeStubs.add(this); 161 } catch (final Exception e) { 162 throw new RemoteException("RemoteMediaDrmBridgeStub cannot create bridge implementation."); 163 } 164 } 165 166 @Override setCallbacks(final IMediaDrmBridgeCallbacks callbacks)167 public synchronized void setCallbacks(final IMediaDrmBridgeCallbacks callbacks) 168 throws RemoteException { 169 if (DEBUG) Log.d(LOGTAG, "setCallbacks()"); 170 assertTrue(mBridge != null); 171 assertTrue(callbacks != null); 172 mCallbacks = callbacks; 173 callbacks.asBinder().linkToDeath(this, 0); 174 mBridge.setCallbacks(new Callbacks(mCallbacks)); 175 } 176 177 @Override createSession(final int createSessionToken, final int promiseId, final String initDataType, final byte[] initData)178 public synchronized void createSession(final int createSessionToken, 179 final int promiseId, 180 final String initDataType, 181 final byte[] initData) throws RemoteException { 182 if (DEBUG) Log.d(LOGTAG, "createSession()"); 183 try { 184 assertTrue(mCallbacks != null); 185 assertTrue(mBridge != null); 186 mBridge.createSession(createSessionToken, 187 promiseId, 188 initDataType, 189 initData); 190 } catch (final Exception e) { 191 Log.e(LOGTAG, "Failed to createSession.", e); 192 mCallbacks.onRejectPromise(promiseId, "Failed to createSession."); 193 } 194 } 195 196 @Override updateSession(final int promiseId, final String sessionId, final byte[] response)197 public synchronized void updateSession(final int promiseId, 198 final String sessionId, 199 final byte[] response) throws RemoteException { 200 if (DEBUG) Log.d(LOGTAG, "updateSession()"); 201 try { 202 assertTrue(mCallbacks != null); 203 assertTrue(mBridge != null); 204 mBridge.updateSession(promiseId, sessionId, response); 205 } catch (final Exception e) { 206 Log.e(LOGTAG, "Failed to updateSession.", e); 207 mCallbacks.onRejectPromise(promiseId, "Failed to updateSession."); 208 } 209 } 210 211 @Override closeSession(final int promiseId, final String sessionId)212 public synchronized void closeSession(final int promiseId, final String sessionId) 213 throws RemoteException { 214 if (DEBUG) Log.d(LOGTAG, "closeSession()"); 215 try { 216 assertTrue(mCallbacks != null); 217 assertTrue(mBridge != null); 218 mBridge.closeSession(promiseId, sessionId); 219 } catch (final Exception e) { 220 Log.e(LOGTAG, "Failed to closeSession.", e); 221 mCallbacks.onRejectPromise(promiseId, "Failed to closeSession."); 222 } 223 } 224 225 // IBinder.DeathRecipient 226 @Override binderDied()227 public synchronized void binderDied() { 228 Log.e(LOGTAG, "Binder died !!"); 229 try { 230 release(); 231 } catch (final Exception e) { 232 Log.e(LOGTAG, "Exception ! Dead recipient !!", e); 233 } 234 } 235 236 @Override release()237 public synchronized void release() { 238 if (DEBUG) Log.d(LOGTAG, "release()"); 239 mBridgeStubs.remove(this); 240 if (mBridge != null) { 241 mBridge.release(); 242 mBridge = null; 243 } 244 mCallbacks.asBinder().unlinkToDeath(this, 0); 245 mCallbacks = null; 246 mStubId = ""; 247 } 248 249 @Override setServerCertificate(final byte[] cert)250 public synchronized void setServerCertificate(final byte[] cert) { 251 try { 252 mBridge.setServerCertificate(cert); 253 } catch (final IllegalStateException e) { 254 Log.e(LOGTAG, "Failed to setServerCertificate.", e); 255 throw e; 256 } 257 } 258 } 259