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 
6 package org.mozilla.gecko.media;
7 
8 import java.util.ArrayList;
9 import java.util.UUID;
10 
11 import org.mozilla.gecko.mozglue.JNIObject;
12 import org.mozilla.gecko.annotation.WrapForJNI;
13 
14 import android.annotation.SuppressLint;
15 import android.media.MediaCrypto;
16 import android.media.MediaDrm;
17 import android.os.Build;
18 import android.util.Log;
19 
20 public final class MediaDrmProxy {
21     private static final String LOGTAG = "GeckoMediaDrmProxy";
22     private static final boolean DEBUG = false;
23     private static final UUID WIDEVINE_SCHEME_UUID =
24             new UUID(0xedef8ba979d64aceL, 0xa3c827dcd51d21edL);
25 
26     private static final String WIDEVINE_KEY_SYSTEM = "com.widevine.alpha";
27     @WrapForJNI
28     private static final String AAC = "audio/mp4a-latm";
29     @WrapForJNI
30     private static final String AVC = "video/avc";
31     @WrapForJNI
32     private static final String VORBIS = "audio/vorbis";
33     @WrapForJNI
34     private static final String VP8 = "video/x-vnd.on2.vp8";
35     @WrapForJNI
36     private static final String VP9 = "video/x-vnd.on2.vp9";
37     @WrapForJNI
38     private static final String OPUS = "audio/opus";
39     @WrapForJNI
40     private static final String FLAC = "audio/flac";
41 
42     public static final ArrayList<MediaDrmProxy> sProxyList = new ArrayList<MediaDrmProxy>();
43 
44     // A flag to avoid using the native object that has been destroyed.
45     private boolean mDestroyed;
46     private GeckoMediaDrm mImpl;
47     private String mDrmStubId;
48 
isSystemSupported()49     private static boolean isSystemSupported() {
50         // Support versions >= Marshmallow
51         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
52             if (DEBUG) Log.d(LOGTAG, "System Not supported !!, current SDK version is " + Build.VERSION.SDK_INT);
53             return false;
54         }
55         return true;
56     }
57 
58     @SuppressLint("NewApi")
59     @WrapForJNI
isSchemeSupported(final String keySystem)60     public static boolean isSchemeSupported(final String keySystem) {
61         if (!isSystemSupported()) {
62             return false;
63         }
64         if (keySystem.equals(WIDEVINE_KEY_SYSTEM)) {
65             return MediaDrm.isCryptoSchemeSupported(WIDEVINE_SCHEME_UUID)
66                     && MediaCrypto.isCryptoSchemeSupported(WIDEVINE_SCHEME_UUID);
67         }
68         if (DEBUG) Log.d(LOGTAG, "isSchemeSupported key sytem = " + keySystem);
69         return false;
70     }
71 
72     @SuppressLint("NewApi")
73     @WrapForJNI
IsCryptoSchemeSupported(final String keySystem, final String container)74     public static boolean IsCryptoSchemeSupported(final String keySystem,
75                                                   final String container) {
76         if (!isSystemSupported()) {
77             return false;
78         }
79         if (keySystem.equals(WIDEVINE_KEY_SYSTEM)) {
80             return MediaDrm.isCryptoSchemeSupported(WIDEVINE_SCHEME_UUID, container);
81         }
82         if (DEBUG) Log.d(LOGTAG, "cannot decrypt key sytem = " + keySystem + ", container = " + container);
83         return false;
84     }
85 
86      // Interface for callback to native.
87     public interface Callbacks {
onSessionCreated(int createSessionToken, int promiseId, byte[] sessionId, byte[] request)88         void onSessionCreated(int createSessionToken,
89                               int promiseId,
90                               byte[] sessionId,
91                               byte[] request);
92 
onSessionUpdated(int promiseId, byte[] sessionId)93         void onSessionUpdated(int promiseId, byte[] sessionId);
94 
onSessionClosed(int promiseId, byte[] sessionId)95         void onSessionClosed(int promiseId, byte[] sessionId);
96 
onSessionMessage(byte[] sessionId, int sessionMessageType, byte[] request)97         void onSessionMessage(byte[] sessionId,
98                               int sessionMessageType,
99                               byte[] request);
100 
onSessionError(byte[] sessionId, String message)101         void onSessionError(byte[] sessionId,
102                             String message);
103 
104         // MediaDrm.KeyStatus is available in API level 23(M)
105         // https://developer.android.com/reference/android/media/MediaDrm.KeyStatus.html
106         // For compatibility between L and M above, we'll unwrap the KeyStatus structure
107         // and store the keyid and status into SessionKeyInfo and pass to native(MediaDrmCDMProxy).
onSessionBatchedKeyChanged(byte[] sessionId, SessionKeyInfo[] keyInfos)108         void onSessionBatchedKeyChanged(byte[] sessionId,
109                                         SessionKeyInfo[] keyInfos);
110 
onRejectPromise(int promiseId, String message)111         void onRejectPromise(int promiseId,
112                              String message);
113     } // Callbacks
114 
115     public static class NativeMediaDrmProxyCallbacks extends JNIObject implements Callbacks {
116         @WrapForJNI(calledFrom = "gecko")
NativeMediaDrmProxyCallbacks()117         NativeMediaDrmProxyCallbacks() {}
118 
119         @Override
120         @WrapForJNI(dispatchTo = "gecko")
onSessionCreated(int createSessionToken, int promiseId, byte[] sessionId, byte[] request)121         public native void onSessionCreated(int createSessionToken,
122                                             int promiseId,
123                                             byte[] sessionId,
124                                             byte[] request);
125 
126         @Override
127         @WrapForJNI(dispatchTo = "gecko")
onSessionUpdated(int promiseId, byte[] sessionId)128         public native void onSessionUpdated(int promiseId, byte[] sessionId);
129 
130         @Override
131         @WrapForJNI(dispatchTo = "gecko")
onSessionClosed(int promiseId, byte[] sessionId)132         public native void onSessionClosed(int promiseId, byte[] sessionId);
133 
134         @Override
135         @WrapForJNI(dispatchTo = "gecko")
onSessionMessage(byte[] sessionId, int sessionMessageType, byte[] request)136         public native void onSessionMessage(byte[] sessionId,
137                                             int sessionMessageType,
138                                             byte[] request);
139 
140         @Override
141         @WrapForJNI(dispatchTo = "gecko")
onSessionError(byte[] sessionId, String message)142         public native void onSessionError(byte[] sessionId,
143                                           String message);
144 
145         @Override
146         @WrapForJNI(dispatchTo = "gecko")
onSessionBatchedKeyChanged(byte[] sessionId, SessionKeyInfo[] keyInfos)147         public native void onSessionBatchedKeyChanged(byte[] sessionId,
148                                                       SessionKeyInfo[] keyInfos);
149 
150         @Override
151         @WrapForJNI(dispatchTo = "gecko")
onRejectPromise(int promiseId, String message)152         public native void onRejectPromise(int promiseId,
153                                            String message);
154 
155         @Override // JNIObject
disposeNative()156         protected void disposeNative() {
157             throw new UnsupportedOperationException();
158         }
159     } // NativeMediaDrmProxyCallbacks
160 
161     // A proxy to callback from LocalMediaDrmBridge to native instance.
162     public static class MediaDrmProxyCallbacks implements GeckoMediaDrm.Callbacks {
163         private final Callbacks mNativeCallbacks;
164         private final MediaDrmProxy mProxy;
165 
MediaDrmProxyCallbacks(final MediaDrmProxy proxy, final Callbacks callbacks)166         public MediaDrmProxyCallbacks(final MediaDrmProxy proxy, final Callbacks callbacks) {
167             mNativeCallbacks = callbacks;
168             mProxy = proxy;
169         }
170 
171         @Override
onSessionCreated(final int createSessionToken, final int promiseId, final byte[] sessionId, final byte[] request)172         public void onSessionCreated(final int createSessionToken,
173                                      final int promiseId,
174                                      final byte[] sessionId,
175                                      final byte[] request) {
176             if (!mProxy.isDestroyed()) {
177                 mNativeCallbacks.onSessionCreated(createSessionToken,
178                                                   promiseId,
179                                                   sessionId,
180                                                   request);
181             }
182         }
183 
184         @Override
onSessionUpdated(final int promiseId, final byte[] sessionId)185         public void onSessionUpdated(final int promiseId, final byte[] sessionId) {
186             if (!mProxy.isDestroyed()) {
187                 mNativeCallbacks.onSessionUpdated(promiseId, sessionId);
188             }
189         }
190 
191         @Override
onSessionClosed(final int promiseId, final byte[] sessionId)192         public void onSessionClosed(final int promiseId, final byte[] sessionId) {
193             if (!mProxy.isDestroyed()) {
194                 mNativeCallbacks.onSessionClosed(promiseId, sessionId);
195             }
196         }
197 
198         @Override
onSessionMessage(final byte[] sessionId, final int sessionMessageType, final byte[] request)199         public void onSessionMessage(final byte[] sessionId,
200                                      final int sessionMessageType,
201                                      final byte[] request) {
202             if (!mProxy.isDestroyed()) {
203                 mNativeCallbacks.onSessionMessage(sessionId, sessionMessageType, request);
204             }
205         }
206 
207         @Override
onSessionError(final byte[] sessionId, final String message)208         public void onSessionError(final byte[] sessionId,
209                                    final String message) {
210             if (!mProxy.isDestroyed()) {
211                 mNativeCallbacks.onSessionError(sessionId, message);
212             }
213         }
214 
215         @Override
onSessionBatchedKeyChanged(final byte[] sessionId, final SessionKeyInfo[] keyInfos)216         public void onSessionBatchedKeyChanged(final byte[] sessionId,
217                                                final SessionKeyInfo[] keyInfos) {
218             if (!mProxy.isDestroyed()) {
219                 mNativeCallbacks.onSessionBatchedKeyChanged(sessionId, keyInfos);
220             }
221         }
222 
223         @Override
onRejectPromise(final int promiseId, final String message)224         public void onRejectPromise(final int promiseId,
225                                     final String message) {
226             if (!mProxy.isDestroyed()) {
227                 mNativeCallbacks.onRejectPromise(promiseId, message);
228             }
229         }
230     } // MediaDrmProxyCallbacks
231 
isDestroyed()232     public boolean isDestroyed() {
233         return mDestroyed;
234     }
235 
236     @WrapForJNI(calledFrom = "gecko")
create(final String keySystem, final Callbacks nativeCallbacks)237     public static MediaDrmProxy create(final String keySystem,
238                                        final Callbacks nativeCallbacks) {
239         final MediaDrmProxy proxy = new MediaDrmProxy(keySystem, nativeCallbacks);
240         return proxy;
241     }
242 
MediaDrmProxy(final String keySystem, final Callbacks nativeCallbacks)243     MediaDrmProxy(final String keySystem, final Callbacks nativeCallbacks) {
244         if (DEBUG) Log.d(LOGTAG, "Constructing MediaDrmProxy");
245         try {
246             mDrmStubId = UUID.randomUUID().toString();
247             final IMediaDrmBridge remoteBridge =
248                 RemoteManager.getInstance().createRemoteMediaDrmBridge(keySystem, mDrmStubId);
249             mImpl = new RemoteMediaDrmBridge(remoteBridge);
250             mImpl.setCallbacks(new MediaDrmProxyCallbacks(this, nativeCallbacks));
251             sProxyList.add(this);
252         } catch (final Exception e) {
253             Log.e(LOGTAG, "Constructing MediaDrmProxy ... error", e);
254         }
255     }
256 
257     @WrapForJNI
createSession(final int createSessionToken, final int promiseId, final String initDataType, final byte[] initData)258     private void createSession(final int createSessionToken,
259                                final int promiseId,
260                                final String initDataType,
261                                final byte[] initData) {
262         if (DEBUG) Log.d(LOGTAG, "createSession, promiseId = " + promiseId);
263         mImpl.createSession(createSessionToken,
264                             promiseId,
265                             initDataType,
266                             initData);
267     }
268 
269     @WrapForJNI
updateSession(final int promiseId, final String sessionId, final byte[] response)270     private void updateSession(final int promiseId, final String sessionId, final byte[] response) {
271         if (DEBUG) Log.d(LOGTAG, "updateSession, primiseId(" + promiseId  + "sessionId(" + sessionId + ")");
272         mImpl.updateSession(promiseId, sessionId, response);
273     }
274 
275     @WrapForJNI
closeSession(final int promiseId, final String sessionId)276     private void closeSession(final int promiseId, final String sessionId) {
277         if (DEBUG) Log.d(LOGTAG, "closeSession, primiseId(" + promiseId  + "sessionId(" + sessionId + ")");
278         mImpl.closeSession(promiseId, sessionId);
279     }
280 
281     @WrapForJNI(calledFrom = "gecko")
getStubId()282     private String getStubId() {
283         return mDrmStubId;
284     }
285 
286     @WrapForJNI
setServerCertificate(final byte[] cert)287     public boolean setServerCertificate(final byte[] cert) {
288         try {
289             mImpl.setServerCertificate(cert);
290             return true;
291         } catch (final RuntimeException e) {
292             return false;
293         }
294     }
295 
296     // Get corresponding MediaCrypto object by a generated UUID for MediaCodec.
297     // Will be called on MediaFormatReader's TaskQueue.
298     @WrapForJNI
getMediaCrypto(final String stubId)299     public static MediaCrypto getMediaCrypto(final String stubId) {
300         for (final MediaDrmProxy proxy : sProxyList) {
301             if (proxy.getStubId().equals(stubId)) {
302                 return proxy.getMediaCryptoFromBridge();
303             }
304         }
305         if (DEBUG) Log.d(LOGTAG, " NULL crytpo ");
306         return null;
307     }
308 
309     @WrapForJNI // Called when natvie object is destroyed.
destroy()310     private void destroy() {
311         if (DEBUG) Log.d(LOGTAG, "destroy!! Native object is destroyed.");
312         if (mDestroyed) {
313             return;
314         }
315         mDestroyed = true;
316         release();
317     }
318 
release()319     private void release() {
320         if (DEBUG) Log.d(LOGTAG, "release");
321         sProxyList.remove(this);
322         mImpl.release();
323     }
324 
getMediaCryptoFromBridge()325     private MediaCrypto getMediaCryptoFromBridge() {
326         return mImpl != null ? mImpl.getMediaCrypto() : null;
327     }
328 }
329