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