1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.media;
6 
7 import android.annotation.SuppressLint;
8 import android.annotation.TargetApi;
9 import android.media.MediaCrypto;
10 import android.media.MediaDrm;
11 import android.media.MediaDrm.MediaDrmStateException;
12 import android.os.Build;
13 
14 import org.chromium.base.ApiCompatibilityUtils;
15 import org.chromium.base.Callback;
16 import org.chromium.base.Log;
17 import org.chromium.base.annotations.CalledByNative;
18 import org.chromium.base.annotations.JNINamespace;
19 import org.chromium.base.annotations.MainDex;
20 import org.chromium.base.annotations.NativeMethods;
21 import org.chromium.media.MediaDrmSessionManager.SessionId;
22 import org.chromium.media.MediaDrmSessionManager.SessionInfo;
23 
24 import java.lang.reflect.Method;
25 import java.util.ArrayDeque;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Queue;
31 import java.util.UUID;
32 
33 // Implementation Notes of MediaDrmBridge:
34 //
35 // MediaCrypto Creation: If requiresMediaCrypto is true, the caller is guaranteed to wait until
36 // MediaCrypto is created to call any other methods. A mMediaCryptoSession is opened after MediaDrm
37 // is created. This session will NOT be added to mSessionManager and will only be used to create the
38 // MediaCrypto object. createMediaCrypto() may trigger the provisioning process, where MediaCrypto
39 // creation will resume after provisioning completes.
40 //
41 // Unprovision: If requiresMediaCrypto is false, MediaDrmBridge is not created for playback.
42 // Instead, it's created to unprovision the device/origin, which is only supported on newer Android
43 // versions. unprovision() is triggered when user clears media licenses.
44 //
45 // NotProvisionedException: If this exception is thrown in operations other than
46 // createMediaCrypto(), we will fail that operation and not trying to provision again.
47 //
48 // Session Manager: Each createSession() call creates a new session. All created sessions are
49 // managed in mSessionManager except for mMediaCryptoSession.
50 //
51 // Error Handling: Whenever an unexpected error occurred, we'll call release() to release all
52 // resources immediately, clear all states and fail all pending operations. After that all calls to
53 // this object will fail (e.g. return null or reject the promise). All public APIs and callbacks
54 // should check mMediaBridge to make sure release() hasn't been called.
55 
56 /**
57  * A wrapper of the android MediaDrm class. Each MediaDrmBridge manages multiple sessions for
58  * MediaCodecAudioDecoders or MediaCodecVideoDecoders.
59  */
60 @JNINamespace("media")
61 @MainDex
62 @SuppressLint("WrongConstant")
63 public class MediaDrmBridge {
64     private static final String TAG = "media";
65     private static final String SECURITY_LEVEL = "securityLevel";
66     private static final String SERVER_CERTIFICATE = "serviceCertificate";
67     private static final String ORIGIN = "origin";
68     private static final String PRIVACY_MODE = "privacyMode";
69     private static final String SESSION_SHARING = "sessionSharing";
70     private static final String ENABLE = "enable";
71     private static final long INVALID_NATIVE_MEDIA_DRM_BRIDGE = 0;
72     private static final String FIRST_API_LEVEL = "ro.product.first_api_level";
73 
74     // Scheme UUID for Widevine. See http://dashif.org/identifiers/protection/
75     private static final UUID WIDEVINE_UUID =
76             UUID.fromString("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed");
77 
78     // On Android L and before, MediaDrm doesn't support KeyStatus at all. On later Android
79     // versions, key IDs are not available on sessions where getKeyRequest() has been called with
80     // KEY_TYPE_RELEASE. In these cases, the EME spec recommends to use a one-byte key ID 0:
81     // "Some older platforms may contain Key System implementations that do not expose key IDs,
82     // making it impossible to provide a compliant user agent implementation. To maximize
83     // interoperability, user agent implementations exposing such CDMs should implement this member
84     // as follows: Whenever a non-empty list is appropriate, such as when the key session
85     // represented by this object may contain key(s), populate the map with a single pair containing
86     // the one-byte key ID 0 and the MediaKeyStatus most appropriate for the aggregated status of
87     // this object."
88     // See details: https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-keystatuses
89     private static final byte[] DUMMY_KEY_ID = new byte[] {0};
90 
91     // Special provision response to remove the cert.
92     private static final byte[] UNPROVISION = ApiCompatibilityUtils.getBytesUtf8("unprovision");
93 
94     private MediaDrm mMediaDrm;
95     private MediaCrypto mMediaCrypto;
96     private long mNativeMediaDrmBridge;
97     private UUID mSchemeUUID;
98     private final boolean mRequiresMediaCrypto;
99 
100     // A session only for the purpose of creating a MediaCrypto object. Created
101     // after construction, or after the provisioning process is successfully
102     // completed. No getKeyRequest() should be called on |mMediaCryptoSession|.
103     private SessionId mMediaCryptoSession;
104 
105     // The map of all opened sessions (excluding mMediaCryptoSession) to their
106     // associated meta data, e.g. mime types, key types.
107     private MediaDrmSessionManager mSessionManager;
108 
109     // The persistent storage to record origin provisioning informations.
110     private MediaDrmStorageBridge mStorage;
111 
112     // Whether the current MediaDrmBridge instance is waiting for provisioning response.
113     private boolean mProvisioningPending;
114 
115     // Current 'ORIGIN" setting.
116     private String mOrigin;
117 
118     // Boolean to track if 'ORIGIN' is set in MediaDrm.
119     private boolean mOriginSet;
120 
121     private SessionEventDeferrer mSessionEventDeferrer;
122 
123     // Defer the creation of MediaCryptor creation. Only used when mRequiresMediaCrypto is true.
124     private static final MediaCryptoDeferrer sMediaCryptoDeferrer = new MediaCryptoDeferrer();
125 
126     private static class MediaCryptoDeferrer {
127         // Whether any MediaDrmBridge instance is waiting for provisioning response.
128         private boolean mIsProvisioning;
129 
130         // Pending events to fire after provisioning is finished.
131         private final Queue<Runnable> mEventHandlers;
132 
MediaCryptoDeferrer()133         MediaCryptoDeferrer() {
134             mIsProvisioning = false;
135             mEventHandlers = new ArrayDeque<Runnable>();
136         }
137 
isProvisioning()138         boolean isProvisioning() {
139             return mIsProvisioning;
140         }
141 
onProvisionStarted()142         void onProvisionStarted() {
143             assert !mIsProvisioning;
144             mIsProvisioning = true;
145         }
146 
defer(Runnable handler)147         void defer(Runnable handler) {
148             assert mIsProvisioning;
149             mEventHandlers.add(handler);
150         }
151 
onProvisionDone()152         void onProvisionDone() {
153             assert mIsProvisioning;
154             mIsProvisioning = false;
155 
156             // This will cause createMediaCrypto() on another MediaDrmBridge object and could cause
157             // reentrance into the shared static sMediaCryptoDeferrer. For example, during
158             // createMediaCrypto(), we could hit NotProvisionedException again, and call
159             // isProvisioning() to check whether it can start provisioning or not. If so, it'll
160             // call onProvisionStarted(). To avoid the case where we call createMediaCrypto() and
161             // then immediately call defer(), we'll return early whenever mIsProvisioning becomes
162             // true.
163             while (!mEventHandlers.isEmpty()) {
164                 Log.d(TAG, "run deferred CreateMediaCrypto() calls");
165                 Runnable r = mEventHandlers.element();
166                 mEventHandlers.remove();
167 
168                 r.run();
169 
170                 if (mIsProvisioning) {
171                     Log.d(TAG, "provision triggerred while running deferred CreateMediaCrypto()");
172                     return;
173                 }
174             }
175         }
176     }
177 
178     // Block MediaDrm event for |mSessionId|. MediaDrm may fire event before the
179     // functions return. This may break Chromium CDM API's assumption. For
180     // example, when loading session, 'restoreKeys' will trigger key status
181     // change event. But the session isn't known to Chromium CDM because the
182     // promise isn't resolved. The class can block and collect these events and
183     // fire these events later.
184     private static class SessionEventDeferrer {
185         private final SessionId mSessionId;
186         private final ArrayList<Runnable> mEventHandlers;
187 
SessionEventDeferrer(SessionId sessionId)188         SessionEventDeferrer(SessionId sessionId) {
189             mSessionId = sessionId;
190             mEventHandlers = new ArrayList<>();
191         }
192 
shouldDefer(SessionId sessionId)193         boolean shouldDefer(SessionId sessionId) {
194             return mSessionId.isEqual(sessionId);
195         }
196 
defer(Runnable handler)197         void defer(Runnable handler) {
198             mEventHandlers.add(handler);
199         }
200 
fire()201         void fire() {
202             for (Runnable r : mEventHandlers) {
203                 r.run();
204             }
205 
206             mEventHandlers.clear();
207         }
208     }
209 
210     /**
211      *  An equivalent of MediaDrm.KeyStatus, which is only available on M+.
212      */
213     @MainDex
214     private static class KeyStatus {
215         private final byte[] mKeyId;
216         private final int mStatusCode;
217 
KeyStatus(byte[] keyId, int statusCode)218         private KeyStatus(byte[] keyId, int statusCode) {
219             mKeyId = keyId;
220             mStatusCode = statusCode;
221         }
222 
223         @CalledByNative("KeyStatus")
getKeyId()224         private byte[] getKeyId() {
225             return mKeyId;
226         }
227 
228         @CalledByNative("KeyStatus")
getStatusCode()229         private int getStatusCode() {
230             return mStatusCode;
231         }
232     }
233 
234     /**
235      *  Creates a dummy single element list of KeyStatus with a dummy key ID and
236      *  the specified keyStatus.
237      */
getDummyKeysInfo(int statusCode)238     private static List<KeyStatus> getDummyKeysInfo(int statusCode) {
239         List<KeyStatus> keysInfo = new ArrayList<KeyStatus>();
240         keysInfo.add(new KeyStatus(DUMMY_KEY_ID, statusCode));
241         return keysInfo;
242     }
243 
getUUIDFromBytes(byte[] data)244     private static UUID getUUIDFromBytes(byte[] data) {
245         if (data.length != 16) {
246             return null;
247         }
248         long mostSigBits = 0;
249         long leastSigBits = 0;
250         for (int i = 0; i < 8; i++) {
251             mostSigBits = (mostSigBits << 8) | (data[i] & 0xff);
252         }
253         for (int i = 8; i < 16; i++) {
254             leastSigBits = (leastSigBits << 8) | (data[i] & 0xff);
255         }
256         return new UUID(mostSigBits, leastSigBits);
257     }
258 
isNativeMediaDrmBridgeValid()259     private boolean isNativeMediaDrmBridgeValid() {
260         return mNativeMediaDrmBridge != INVALID_NATIVE_MEDIA_DRM_BRIDGE;
261     }
262 
isWidevine()263     private boolean isWidevine() {
264         return mSchemeUUID.equals(WIDEVINE_UUID);
265     }
266 
267     @TargetApi(Build.VERSION_CODES.M)
MediaDrmBridge(UUID schemeUUID, boolean requiresMediaCrypto, long nativeMediaDrmBridge, long nativeMediaDrmStorageBridge)268     private MediaDrmBridge(UUID schemeUUID, boolean requiresMediaCrypto, long nativeMediaDrmBridge,
269             long nativeMediaDrmStorageBridge) throws android.media.UnsupportedSchemeException {
270         mSchemeUUID = schemeUUID;
271         mMediaDrm = new MediaDrm(schemeUUID);
272         mRequiresMediaCrypto = requiresMediaCrypto;
273 
274         mNativeMediaDrmBridge = nativeMediaDrmBridge;
275         assert isNativeMediaDrmBridgeValid();
276 
277         mStorage = new MediaDrmStorageBridge(nativeMediaDrmStorageBridge);
278         mSessionManager = new MediaDrmSessionManager(mStorage);
279 
280         mProvisioningPending = false;
281 
282         mMediaDrm.setOnEventListener(new EventListener());
283         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
284             mMediaDrm.setOnExpirationUpdateListener(new ExpirationUpdateListener(), null);
285             mMediaDrm.setOnKeyStatusChangeListener(new KeyStatusChangeListener(), null);
286         }
287 
288         if (isWidevine()) {
289             mMediaDrm.setPropertyString(PRIVACY_MODE, ENABLE);
290             mMediaDrm.setPropertyString(SESSION_SHARING, ENABLE);
291         }
292     }
293 
294     /**
295      * Create a MediaCrypto object.
296      *
297      * @return false upon fatal error in creating MediaCrypto. Returns true
298      * otherwise, including the following two cases:
299      *   1. MediaCrypto is successfully created and notified.
300      *   2. Device is not provisioned and MediaCrypto creation will be tried
301      *      again after the provisioning process is completed.
302      *
303      *  When false is returned, release() is called within the function, which
304      *  will notify the native code with a null MediaCrypto, if needed.
305      */
createMediaCrypto()306     private boolean createMediaCrypto() {
307         assert mMediaDrm != null;
308         assert !mProvisioningPending;
309         assert mMediaCryptoSession == null;
310 
311         // Open media crypto session.
312         byte[] mediaCryptoSessionDrmId = null;
313         try {
314             mediaCryptoSessionDrmId = openSession();
315         } catch (android.media.NotProvisionedException e) {
316             Log.d(TAG, "Not provisioned during openSession()");
317 
318             if (!sMediaCryptoDeferrer.isProvisioning()) {
319                 return startProvisioning();
320             }
321 
322             // Cannot provision. Defer MediaCrypto creation and try again later.
323             Log.d(TAG, "defer CreateMediaCrypto() calls");
324             sMediaCryptoDeferrer.defer(new Runnable() {
325                 @Override
326                 public void run() {
327                     createMediaCrypto();
328                 }
329             });
330 
331             return true;
332         }
333 
334         if (mediaCryptoSessionDrmId == null) {
335             Log.e(TAG, "Cannot create MediaCrypto Session.");
336             // No need to release() here since openSession() does so on failure.
337             return false;
338         }
339 
340         mMediaCryptoSession = SessionId.createTemporarySessionId(mediaCryptoSessionDrmId);
341 
342         Log.d(TAG, "MediaCrypto Session created: %s", mMediaCryptoSession.toHexString());
343 
344         // Create MediaCrypto object.
345         try {
346             if (MediaCrypto.isCryptoSchemeSupported(mSchemeUUID)) {
347                 mMediaCrypto = new MediaCrypto(mSchemeUUID, mMediaCryptoSession.drmId());
348                 Log.d(TAG, "MediaCrypto successfully created!");
349                 onMediaCryptoReady(mMediaCrypto);
350                 return true;
351             } else {
352                 Log.e(TAG, "Cannot create MediaCrypto for unsupported scheme.");
353             }
354         } catch (android.media.MediaCryptoException e) {
355             Log.e(TAG, "Cannot create MediaCrypto", e);
356         }
357 
358         release();
359         return false;
360     }
361 
362     /**
363      * Open a new session.
364      *
365      * @return ID of the session opened. Returns null if unexpected error happened.
366      */
openSession()367     private byte[] openSession() throws android.media.NotProvisionedException {
368         assert mMediaDrm != null;
369         try {
370             byte[] sessionId = mMediaDrm.openSession();
371             // Make a clone here in case the underlying byte[] is modified.
372             return sessionId.clone();
373         } catch (java.lang.RuntimeException e) { // TODO(xhwang): Drop this?
374             Log.e(TAG, "Cannot open a new session", e);
375             release();
376             return null;
377         } catch (android.media.NotProvisionedException e) {
378             // Throw NotProvisionedException so that we can startProvisioning().
379             throw e;
380         } catch (android.media.MediaDrmException e) {
381             // Other MediaDrmExceptions (e.g. ResourceBusyException) are not
382             // recoverable.
383             Log.e(TAG, "Cannot open a new session", e);
384             release();
385             return null;
386         }
387     }
388 
389     /**
390      * Check whether the crypto scheme is supported for the given container.
391      * If |containerMimeType| is an empty string, we just return whether
392      * the crypto scheme is supported.
393      *
394      * @return true if the container and the crypto scheme is supported, or
395      * false otherwise.
396      */
397     @CalledByNative
isCryptoSchemeSupported(byte[] schemeUUID, String containerMimeType)398     private static boolean isCryptoSchemeSupported(byte[] schemeUUID, String containerMimeType) {
399         UUID cryptoScheme = getUUIDFromBytes(schemeUUID);
400 
401         if (containerMimeType.isEmpty()) {
402             return MediaDrm.isCryptoSchemeSupported(cryptoScheme);
403         }
404 
405         return MediaDrm.isCryptoSchemeSupported(cryptoScheme, containerMimeType);
406     }
407 
408     /**
409      * Returns the first API level for this product.
410      *
411      * @return the converted value for FIRST_API_LEVEL if available,
412      * 0 otherwise.
413      */
414     @CalledByNative
getFirstApiLevel()415     private static int getFirstApiLevel() {
416         int firstApiLevel = 0;
417         try {
418             final Class<?> systemProperties = Class.forName("android.os.SystemProperties");
419             final Method getInt = systemProperties.getMethod("getInt", String.class, int.class);
420             firstApiLevel = (Integer) getInt.invoke(null, FIRST_API_LEVEL, 0);
421         } catch (Exception e) {
422             Log.e(TAG, "Exception while getting system property %s. Using default.",
423                     FIRST_API_LEVEL, e);
424             firstApiLevel = 0;
425         }
426         return firstApiLevel;
427     }
428 
429     /**
430      * Create a new MediaDrmBridge from the crypto scheme UUID.
431      *
432      * @param schemeUUID Crypto scheme UUID.
433      * @param securityOrigin Security origin. Empty value means no need for origin isolated storage.
434      * @param securityLevel Security level. If empty, the default one should be used.
435      * @param nativeMediaDrmBridge Native object of this class.
436      * @param nativeMediaDrmStorageBridge Native object of persistent storage.
437      */
438     @CalledByNative
create(byte[] schemeUUID, String securityOrigin, String securityLevel, boolean requiresMediaCrypto, long nativeMediaDrmBridge, long nativeMediaDrmStorageBridge)439     private static MediaDrmBridge create(byte[] schemeUUID, String securityOrigin,
440             String securityLevel, boolean requiresMediaCrypto, long nativeMediaDrmBridge,
441             long nativeMediaDrmStorageBridge) {
442         Log.i(TAG, "Create MediaDrmBridge with level %s and origin %s", securityLevel,
443                 securityOrigin);
444 
445         MediaDrmBridge mediaDrmBridge = null;
446         try {
447             UUID cryptoScheme = getUUIDFromBytes(schemeUUID);
448             if (cryptoScheme == null || !MediaDrm.isCryptoSchemeSupported(cryptoScheme)) {
449                 return null;
450             }
451 
452             mediaDrmBridge = new MediaDrmBridge(cryptoScheme, requiresMediaCrypto,
453                     nativeMediaDrmBridge, nativeMediaDrmStorageBridge);
454         } catch (android.media.UnsupportedSchemeException e) {
455             Log.e(TAG, "Unsupported DRM scheme", e);
456             return null;
457         } catch (java.lang.IllegalArgumentException e) {
458             Log.e(TAG, "Failed to create MediaDrmBridge", e);
459             return null;
460         } catch (java.lang.IllegalStateException e) {
461             Log.e(TAG, "Failed to create MediaDrmBridge", e);
462             return null;
463         }
464 
465         if (!securityLevel.isEmpty() && !mediaDrmBridge.setSecurityLevel(securityLevel)) {
466             mediaDrmBridge.release();
467             return null;
468         }
469 
470         if (!securityOrigin.isEmpty() && !mediaDrmBridge.setOrigin(securityOrigin)) {
471             mediaDrmBridge.release();
472             return null;
473         }
474 
475         // When session support is required, we need to create MediaCrypto to
476         // finish the CDM creation process. This may trigger the provisioning
477         // process, in which case MediaCrypto will be created after provision
478         // is finished.
479         if (requiresMediaCrypto && !mediaDrmBridge.createMediaCrypto()) {
480             // No need to call release() as createMediaCrypto() does if it fails.
481             return null;
482         }
483 
484         return mediaDrmBridge;
485     }
486 
487     /**
488      * Set the security origin for the MediaDrm. All information should be isolated for different
489      * origins, e.g. certificates, licenses.
490      */
setOrigin(String origin)491     private boolean setOrigin(String origin) {
492         assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
493         Log.d(TAG, "Set origin: %s", origin);
494 
495         if (!isWidevine()) {
496             Log.d(TAG, "Property " + ORIGIN + " isn't supported");
497             return true;
498         }
499 
500         assert mMediaDrm != null;
501         assert !origin.isEmpty();
502 
503         try {
504             mMediaDrm.setPropertyString(ORIGIN, origin);
505             mOrigin = origin;
506             mOriginSet = true;
507             return true;
508         } catch (java.lang.IllegalArgumentException e) {
509             Log.e(TAG, "Failed to set security origin %s", origin, e);
510         } catch (java.lang.IllegalStateException e) {
511             Log.e(TAG, "Failed to set security origin %s", origin, e);
512         }
513 
514         Log.e(TAG, "Security origin %s not supported!", origin);
515         return false;
516     }
517 
518     /**
519      * Set the security level that the MediaDrm object uses.
520      * This function should be called right after we construct MediaDrmBridge
521      * and before we make any other calls.
522      *
523      * @param securityLevel Security level to be set.
524      * @return whether the security level was successfully set.
525      */
setSecurityLevel(String securityLevel)526     private boolean setSecurityLevel(String securityLevel) {
527         if (!isWidevine()) {
528             Log.d(TAG, "Security level is not supported.");
529             return true;
530         }
531 
532         assert mMediaDrm != null;
533         assert !securityLevel.isEmpty();
534 
535         String currentSecurityLevel = getSecurityLevel();
536         if (currentSecurityLevel.equals("")) {
537             // Failure logged by getSecurityLevel().
538             return false;
539         }
540 
541         Log.d(TAG, "Security level: current %s, new %s", currentSecurityLevel, securityLevel);
542         if (securityLevel.equals(currentSecurityLevel)) {
543             // No need to set the same security level again. This is not just
544             // a shortcut! Setting the same security level actually causes an
545             // exception in MediaDrm!
546             return true;
547         }
548 
549         try {
550             mMediaDrm.setPropertyString(SECURITY_LEVEL, securityLevel);
551             return true;
552         } catch (java.lang.IllegalArgumentException e) {
553         } catch (java.lang.IllegalStateException e) {
554         }
555 
556         Log.e(TAG, "Security level %s not supported!", securityLevel);
557         return false;
558     }
559 
560     /**
561      * Set the server certificate.
562      *
563      * @param certificate Server certificate to be set.
564      * @return whether the server certificate was successfully set.
565      */
566     @CalledByNative
setServerCertificate(byte[] certificate)567     private boolean setServerCertificate(byte[] certificate) {
568         if (!isWidevine()) {
569             Log.d(TAG, "Setting server certificate is not supported.");
570             return true;
571         }
572 
573         try {
574             mMediaDrm.setPropertyByteArray(SERVER_CERTIFICATE, certificate);
575             return true;
576         } catch (java.lang.IllegalArgumentException e) {
577             Log.e(TAG, "Failed to set server certificate", e);
578         } catch (java.lang.IllegalStateException e) {
579             Log.e(TAG, "Failed to set server certificate", e);
580         }
581 
582         return false;
583     }
584 
585     /**
586      * Provision the current origin. Normally provisioning will be triggered
587      * automatically when MediaCrypto is needed (in the constructor).
588      * However, this is available to preprovision an origin separately.
589      * MediaDrmBridgeJni.get().onProvisioningComplete() will be called indicating success/failure.
590      */
591     @CalledByNative
provision()592     private void provision() {
593         // This should only be called if no MediaCrypto needed.
594         assert mMediaDrm != null;
595         assert !mProvisioningPending;
596         assert !mRequiresMediaCrypto;
597 
598         // Provision only works for origin isolated storage.
599         if (!mOriginSet) {
600             Log.e(TAG, "Calling provision() without an origin.");
601             MediaDrmBridgeJni.get().onProvisioningComplete(
602                     mNativeMediaDrmBridge, MediaDrmBridge.this, false);
603             return;
604         }
605 
606         // The security level used for provisioning cannot be set and is cached from when a need for
607         // provisioning is last detected. So if we call startProvisioning() it will use the default
608         // security level, which may not match the security level needed. As a result this code must
609         // call openSession(), which will result in the security level being cached. We don't care
610         // about the session, so if it opens simply close it.
611         try {
612             // This will throw a NotProvisionedException if provisioning needed. If it succeeds,
613             // assume this origin ID is already provisioned.
614             byte[] drmId = openSession();
615 
616             // Provisioning is not required. If a session was actually opened, close it.
617             if (drmId != null) {
618                 SessionId sessionId = SessionId.createTemporarySessionId(drmId);
619                 closeSessionNoException(sessionId);
620             }
621 
622             // Indicate that provisioning succeeded.
623             MediaDrmBridgeJni.get().onProvisioningComplete(
624                     mNativeMediaDrmBridge, MediaDrmBridge.this, true);
625 
626         } catch (android.media.NotProvisionedException e) {
627             if (!startProvisioning()) {
628                 // Indicate that provisioning failed.
629                 MediaDrmBridgeJni.get().onProvisioningComplete(
630                         mNativeMediaDrmBridge, MediaDrmBridge.this, false);
631             }
632         }
633     }
634 
635     /**
636      * Unprovision the current origin, a.k.a removing the cert for current origin.
637      */
638     @CalledByNative
unprovision()639     private void unprovision() {
640         if (mMediaDrm == null) {
641             return;
642         }
643 
644         // Unprovision only works for origin isolated storage.
645         if (!mOriginSet) {
646             return;
647         }
648 
649         provideProvisionResponse(UNPROVISION);
650     }
651 
652     /**
653      * Destroy the MediaDrmBridge object.
654      */
655     @CalledByNative
destroy()656     private void destroy() {
657         mNativeMediaDrmBridge = INVALID_NATIVE_MEDIA_DRM_BRIDGE;
658         if (mMediaDrm != null) {
659             release();
660         }
661     }
662 
663     /**
664      * Release all allocated resources and finish all pending operations.
665      */
release()666     private void release() {
667         // Note that mNativeMediaDrmBridge may have already been reset (see destroy()).
668 
669         assert mMediaDrm != null;
670 
671         // Close all open sessions.
672         for (SessionId sessionId : mSessionManager.getAllSessionIds()) {
673             try {
674                 // Some implementations don't have removeKeys, crbug/475632
675                 mMediaDrm.removeKeys(sessionId.drmId());
676             } catch (Exception e) {
677                 Log.e(TAG, "removeKeys failed: ", e);
678             }
679 
680             closeSessionNoException(sessionId);
681             onSessionClosed(sessionId);
682         }
683         mSessionManager = new MediaDrmSessionManager(mStorage);
684 
685         // Close mMediaCryptoSession if it's open.
686         if (mMediaCryptoSession != null) {
687             closeSessionNoException(mMediaCryptoSession);
688             mMediaCryptoSession = null;
689         }
690 
691         if (mMediaDrm != null) {
692             mMediaDrm.release();
693             mMediaDrm = null;
694         }
695 
696         if (mMediaCrypto != null) {
697             mMediaCrypto.release();
698             mMediaCrypto = null;
699         } else {
700             // MediaCrypto never notified. Notify a null one now.
701             onMediaCryptoReady(null);
702         }
703     }
704 
705     /**
706      * Get a key request.
707      *
708      * @param sessionId ID of session on which we need to get the key request.
709      * @param data Data needed to get the key request.
710      * @param mime Mime type to get the key request.
711      * @param keyType Key type for the requested key.
712      * @param optionalParameters Optional parameters to pass to the DRM plugin.
713      *
714      * @return the key request.
715      */
getKeyRequest(SessionId sessionId, byte[] data, String mime, int keyType, HashMap<String, String> optionalParameters)716     private MediaDrm.KeyRequest getKeyRequest(SessionId sessionId, byte[] data, String mime,
717             int keyType, HashMap<String, String> optionalParameters)
718             throws android.media.NotProvisionedException {
719         assert mMediaDrm != null;
720         assert mMediaCryptoSession != null;
721         assert !mProvisioningPending;
722 
723         if (optionalParameters == null) {
724             optionalParameters = new HashMap<String, String>();
725         }
726 
727         MediaDrm.KeyRequest request = null;
728 
729         try {
730             byte[] scopeId =
731                     keyType == MediaDrm.KEY_TYPE_RELEASE ? sessionId.keySetId() : sessionId.drmId();
732             assert scopeId != null;
733             request = mMediaDrm.getKeyRequest(scopeId, data, mime, keyType, optionalParameters);
734         } catch (MediaDrmStateException e) {
735             // See b/21307186 for details.
736             Log.e(TAG, "MediaDrmStateException fired during getKeyRequest().", e);
737         }
738 
739         String result = (request != null) ? "successed" : "failed";
740         Log.d(TAG, "getKeyRequest %s!", result);
741 
742         return request;
743     }
744 
745     /**
746      * createSession interface to be called from native using primitive types.
747      * @see createSession(byte[], String, HashMap<String, String>, long)
748      */
749     @CalledByNative
createSessionFromNative(byte[] initData, String mime, int keyType, String[] optionalParamsArray, long promiseId)750     private void createSessionFromNative(byte[] initData, String mime, int keyType,
751             String[] optionalParamsArray, long promiseId) {
752         HashMap<String, String> optionalParameters = new HashMap<String, String>();
753         if (optionalParamsArray != null) {
754             if (optionalParamsArray.length % 2 != 0) {
755                 throw new IllegalArgumentException(
756                         "Additional data array doesn't have equal keys/values");
757             }
758             for (int i = 0; i < optionalParamsArray.length; i += 2) {
759                 optionalParameters.put(optionalParamsArray[i], optionalParamsArray[i + 1]);
760             }
761         }
762         createSession(initData, mime, keyType, optionalParameters, promiseId);
763     }
764 
765     /**
766      * Create a session, and generate a request with |initData| and |mime|.
767      *
768      * @param initData Data needed to generate the key request.
769      * @param mime Mime type.
770      * @param keyType Key type.
771      * @param optionalParameters Additional data to pass to getKeyRequest.
772      * @param promiseId Promise ID for this call.
773      */
createSession(byte[] initData, String mime, int keyType, HashMap<String, String> optionalParameters, long promiseId)774     private void createSession(byte[] initData, String mime, int keyType,
775             HashMap<String, String> optionalParameters, long promiseId) {
776         Log.d(TAG, "createSession()");
777 
778         if (mMediaDrm == null) {
779             Log.e(TAG, "createSession() called when MediaDrm is null.");
780             onPromiseRejected(promiseId, "MediaDrm released previously.");
781             return;
782         }
783 
784         assert mMediaCryptoSession != null;
785         assert !mProvisioningPending;
786 
787         boolean newSessionOpened = false;
788         SessionId sessionId = null;
789         try {
790             byte[] drmId = openSession();
791             if (drmId == null) {
792                 onPromiseRejected(promiseId, "Open session failed.");
793                 return;
794             }
795             newSessionOpened = true;
796             assert keyType == MediaDrm.KEY_TYPE_STREAMING || keyType == MediaDrm.KEY_TYPE_OFFLINE;
797             sessionId = (keyType == MediaDrm.KEY_TYPE_OFFLINE)
798                     ? SessionId.createPersistentSessionId(drmId)
799                     : SessionId.createTemporarySessionId(drmId);
800 
801             MediaDrm.KeyRequest request =
802                     getKeyRequest(sessionId, initData, mime, keyType, optionalParameters);
803             if (request == null) {
804                 closeSessionNoException(sessionId);
805                 onPromiseRejected(promiseId, "Generate request failed.");
806                 return;
807             }
808 
809             // Success!
810             Log.d(TAG, "createSession(): Session (%s) created.", sessionId.toHexString());
811             onPromiseResolvedWithSession(promiseId, sessionId);
812             onSessionMessage(sessionId, request);
813             mSessionManager.put(sessionId, mime, keyType);
814         } catch (android.media.NotProvisionedException e) {
815             Log.e(TAG, "Device not provisioned", e);
816             if (newSessionOpened) {
817                 closeSessionNoException(sessionId);
818             }
819             onPromiseRejected(promiseId, "Device not provisioned during createSession().");
820         }
821     }
822 
823     /**
824      * Search and return the SessionId for raw EME/DRM session id.
825      *
826      * @param emeId Raw EME session Id.
827      * @return SessionId of |emeId| if exists and isn't a MediaCryptoSession, null otherwise.
828      */
getSessionIdByEmeId(byte[] emeId)829     private SessionId getSessionIdByEmeId(byte[] emeId) {
830         if (mMediaCryptoSession == null) {
831             Log.e(TAG, "Session doesn't exist because media crypto session is not created.");
832             return null;
833         }
834 
835         SessionId sessionId = mSessionManager.getSessionIdByEmeId(emeId);
836         if (sessionId == null) {
837             return null;
838         }
839 
840         assert !mMediaCryptoSession.isEqual(sessionId);
841 
842         return sessionId;
843     }
844 
845     /**
846      * Similar with getSessionIdByEmeId, just search for raw DRM session id.
847      */
getSessionIdByDrmId(byte[] drmId)848     private SessionId getSessionIdByDrmId(byte[] drmId) {
849         if (mMediaCryptoSession == null) {
850             Log.e(TAG, "Session doesn't exist because media crypto session is not created.");
851             return null;
852         }
853 
854         SessionId sessionId = mSessionManager.getSessionIdByDrmId(drmId);
855         if (sessionId == null) {
856             return null;
857         }
858 
859         assert !mMediaCryptoSession.isEqual(sessionId);
860 
861         return sessionId;
862     }
863 
864     /**
865      * Close a session that was previously created by createSession().
866      *
867      * @param emeSessionId ID of session to be closed.
868      * @param promiseId Promise ID of this call.
869      */
870     @CalledByNative
closeSession(byte[] emeSessionId, long promiseId)871     private void closeSession(byte[] emeSessionId, long promiseId) {
872         Log.d(TAG, "closeSession()");
873         if (mMediaDrm == null) {
874             onPromiseRejected(promiseId, "closeSession() called when MediaDrm is null.");
875             return;
876         }
877 
878         SessionId sessionId = getSessionIdByEmeId(emeSessionId);
879         if (sessionId == null) {
880             onPromiseRejected(promiseId,
881                     "Invalid sessionId in closeSession(): " + SessionId.toHexString(emeSessionId));
882             return;
883         }
884 
885         try {
886             // Some implementations don't have removeKeys, crbug/475632
887             mMediaDrm.removeKeys(sessionId.drmId());
888         } catch (Exception e) {
889             Log.e(TAG, "removeKeys failed: ", e);
890         }
891 
892         closeSessionNoException(sessionId);
893         mSessionManager.remove(sessionId);
894         onPromiseResolved(promiseId);
895         onSessionClosed(sessionId);
896         Log.d(TAG, "Session %s closed", sessionId.toHexString());
897     }
898 
899     /**
900      * Close the session without worry about the exception, because some
901      * implementations let this method throw exception, crbug/611865.
902      */
closeSessionNoException(SessionId sessionId)903     private void closeSessionNoException(SessionId sessionId) {
904         try {
905             mMediaDrm.closeSession(sessionId.drmId());
906         } catch (Exception e) {
907             Log.e(TAG, "closeSession failed: ", e);
908         }
909     }
910 
911     /**
912      * Update a session with response.
913      *
914      * @param emeSessionId Reference ID of session to be updated.
915      * @param response Response data from the server.
916      * @param promiseId Promise ID of this call.
917      */
918     @CalledByNative
updateSession(byte[] emeSessionId, byte[] response, final long promiseId)919     private void updateSession(byte[] emeSessionId, byte[] response, final long promiseId) {
920         Log.d(TAG, "updateSession()");
921         if (mMediaDrm == null) {
922             onPromiseRejected(promiseId, "updateSession() called when MediaDrm is null.");
923             return;
924         }
925 
926         final SessionId sessionId = getSessionIdByEmeId(emeSessionId);
927         if (sessionId == null) {
928             assert false; // Should never happen.
929             onPromiseRejected(promiseId,
930                     "Invalid session in updateSession: " + SessionId.toHexString(emeSessionId));
931             return;
932         }
933 
934         try {
935             SessionInfo sessionInfo = mSessionManager.get(sessionId);
936             boolean isKeyRelease = sessionInfo.keyType() == MediaDrm.KEY_TYPE_RELEASE;
937 
938             byte[] keySetId = null;
939             if (isKeyRelease) {
940                 Log.d(TAG, "updateSession() for key release");
941                 assert sessionId.keySetId() != null;
942                 mMediaDrm.provideKeyResponse(sessionId.keySetId(), response);
943             } else {
944                 keySetId = mMediaDrm.provideKeyResponse(sessionId.drmId(), response);
945             }
946 
947             KeyUpdatedCallback cb = new KeyUpdatedCallback(sessionId, promiseId, isKeyRelease);
948 
949             if (isKeyRelease) {
950                 mSessionManager.clearPersistentSessionInfo(sessionId, cb);
951             } else if (sessionInfo.keyType() == MediaDrm.KEY_TYPE_OFFLINE && keySetId != null
952                     && keySetId.length > 0) {
953                 mSessionManager.setKeySetId(sessionId, keySetId, cb);
954             } else {
955                 // This can be either temporary license update or server certificate update.
956                 cb.onResult(true);
957             }
958 
959             return;
960         } catch (android.media.NotProvisionedException e) {
961             // TODO(xhwang): Should we handle this?
962             Log.e(TAG, "failed to provide key response", e);
963         } catch (android.media.DeniedByServerException e) {
964             Log.e(TAG, "failed to provide key response", e);
965         } catch (java.lang.IllegalStateException e) {
966             Log.e(TAG, "failed to provide key response", e);
967         }
968         onPromiseRejected(promiseId, "Update session failed.");
969         release();
970     }
971 
972     /**
973      * Load persistent license from storage.
974      */
975     @CalledByNative
976     @TargetApi(Build.VERSION_CODES.M)
loadSession(byte[] emeId, final long promiseId)977     private void loadSession(byte[] emeId, final long promiseId) {
978         Log.d(TAG, "loadSession()");
979         assert !mProvisioningPending;
980 
981         mSessionManager.load(emeId, new Callback<SessionId>() {
982             @Override
983             public void onResult(SessionId sessionId) {
984                 if (sessionId == null) {
985                     onPersistentLicenseNoExist(promiseId);
986                     return;
987                 }
988 
989                 loadSessionWithLoadedStorage(sessionId, promiseId);
990             }
991         });
992     }
993 
994     /**
995      * Load session back to memory with MediaDrm. Load persistent storage
996      * before calling this. It will fail if persistent storage isn't loaded.
997      */
998     @TargetApi(Build.VERSION_CODES.M)
loadSessionWithLoadedStorage(SessionId sessionId, final long promiseId)999     private void loadSessionWithLoadedStorage(SessionId sessionId, final long promiseId) {
1000         byte[] drmId = null;
1001         try {
1002             drmId = openSession();
1003             if (drmId == null) {
1004                 onPromiseRejected(promiseId, "Failed to open session to load license.");
1005                 return;
1006             }
1007 
1008             mSessionManager.setDrmId(sessionId, drmId);
1009             assert Arrays.equals(sessionId.drmId(), drmId);
1010 
1011             SessionInfo sessionInfo = mSessionManager.get(sessionId);
1012 
1013             // If persistent license (KEY_TYPE_OFFLINE) is released but we don't receive the ack
1014             // from the server, we should avoid restoring the keys. Report success to JS so that
1015             // they can release it again.
1016             if (sessionInfo.keyType() == MediaDrm.KEY_TYPE_RELEASE) {
1017                 Log.w(TAG, "Persistent license is waiting for release ack.");
1018                 onPromiseResolvedWithSession(promiseId, sessionId);
1019 
1020                 // Report keystatuseschange event to JS. Ideally we should report the event with
1021                 // list of known key IDs. However we can't get the key IDs from MediaDrm. Just
1022                 // report with dummy key IDs.
1023                 onSessionKeysChange(sessionId,
1024                         getDummyKeysInfo(MediaDrm.KeyStatus.STATUS_EXPIRED).toArray(),
1025                         false /* hasAdditionalUsableKey */, true /* isKeyRelease */);
1026                 return;
1027             }
1028 
1029             assert sessionInfo.keyType() == MediaDrm.KEY_TYPE_OFFLINE;
1030 
1031             // Defer event handlers until license is loaded.
1032             assert mSessionEventDeferrer == null;
1033             mSessionEventDeferrer = new SessionEventDeferrer(sessionId);
1034 
1035             assert sessionId.keySetId() != null;
1036             mMediaDrm.restoreKeys(sessionId.drmId(), sessionId.keySetId());
1037 
1038             onPromiseResolvedWithSession(promiseId, sessionId);
1039 
1040             mSessionEventDeferrer.fire();
1041             mSessionEventDeferrer = null;
1042         } catch (android.media.NotProvisionedException e) {
1043             // If device isn't provisioned, storage loading should fail.
1044             Log.w(TAG, "Persistent license load fail because origin isn't provisioned.");
1045             onPersistentLicenseLoadFail(sessionId, promiseId);
1046         } catch (java.lang.IllegalStateException e) {
1047             assert sessionId.drmId() != null;
1048             onPersistentLicenseLoadFail(sessionId, promiseId);
1049         }
1050     }
1051 
onPersistentLicenseNoExist(long promiseId)1052     private void onPersistentLicenseNoExist(long promiseId) {
1053         // Chromium CDM API requires resolve the promise with empty session id for non-exist
1054         // license. See media/base/content_decryption_module.h LoadSession for more details.
1055         onPromiseResolvedWithSession(promiseId, SessionId.createNoExistSessionId());
1056     }
1057 
1058     // If persistent license load fails, we want to clean the storage and report it to JS as license
1059     // doesn't exist.
onPersistentLicenseLoadFail(SessionId sessionId, final long promiseId)1060     private void onPersistentLicenseLoadFail(SessionId sessionId, final long promiseId) {
1061         closeSessionNoException(sessionId);
1062         mSessionManager.clearPersistentSessionInfo(sessionId, new Callback<Boolean>() {
1063             @Override
1064             public void onResult(Boolean success) {
1065                 if (!success) {
1066                     Log.w(TAG, "Failed to clear persistent storage for non-exist license");
1067                 }
1068 
1069                 onPersistentLicenseNoExist(promiseId);
1070             }
1071         });
1072     }
1073 
1074     /**
1075      * Remove session from device. This will mark the key as released and
1076      * generate a key release request. The license is removed from the device
1077      * when the session is updated with a license release response.
1078      */
1079     @CalledByNative
removeSession(byte[] emeId, final long promiseId)1080     private void removeSession(byte[] emeId, final long promiseId) {
1081         Log.d(TAG, "removeSession()");
1082         SessionId sessionId = getSessionIdByEmeId(emeId);
1083 
1084         if (sessionId == null) {
1085             onPromiseRejected(promiseId, "Session doesn't exist");
1086             return;
1087         }
1088 
1089         final SessionInfo sessionInfo = mSessionManager.get(sessionId);
1090         if (sessionInfo.keyType() == MediaDrm.KEY_TYPE_STREAMING) {
1091             // TODO(yucliu): Support 'remove' of temporary session.
1092             onPromiseRejected(promiseId, "Removing temporary session isn't implemented");
1093             return;
1094         }
1095 
1096         assert sessionId.keySetId() != null;
1097 
1098         // Persist the key type before removing the keys completely.
1099         // 1. If we fails to persist the key type, both the persistent storage and MediaDrm think
1100         // the keys are alive. JS can just remove the session again.
1101         // 2. If we are able to persist the key type but don't get the callback, persistent storage
1102         // thinks keys are removed but MediaDrm thinks keys are alive. JS thinks keys are removed
1103         // next time it loads the keys, which matches the expectation of this function.
1104         mSessionManager.setKeyType(sessionId, MediaDrm.KEY_TYPE_RELEASE, new Callback<Boolean>() {
1105             @Override
1106             public void onResult(Boolean success) {
1107                 if (!success) {
1108                     onPromiseRejected(promiseId, "Fail to update persistent storage");
1109                     return;
1110                 }
1111 
1112                 doRemoveSession(sessionId, sessionInfo.mimeType(), promiseId);
1113             }
1114         });
1115     }
1116 
doRemoveSession(SessionId sessionId, String mimeType, long promiseId)1117     private void doRemoveSession(SessionId sessionId, String mimeType, long promiseId) {
1118         try {
1119             // Get key release request.
1120             MediaDrm.KeyRequest request =
1121                     getKeyRequest(sessionId, null, mimeType, MediaDrm.KEY_TYPE_RELEASE, null);
1122 
1123             if (request == null) {
1124                 onPromiseRejected(promiseId, "Fail to generate key release request");
1125                 return;
1126             }
1127 
1128             // According to EME spec:
1129             // https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-remove
1130             // 5.5 ... run the Queue a "message" Event ...
1131             // 5.6 Resolve promise
1132             // Since event is queued, JS will receive event after promise is
1133             // resolved. So resolve the promise before firing the event here.
1134             onPromiseResolved(promiseId);
1135             onSessionMessage(sessionId, request);
1136         } catch (android.media.NotProvisionedException e) {
1137             Log.e(TAG, "removeSession called on unprovisioned device");
1138             onPromiseRejected(promiseId, "Unknown failure");
1139         }
1140     }
1141 
1142     /**
1143      * Return the security level of this DRM object. In case of failure this
1144      * returns the empty string, which is treated by the native side as
1145      * "DEFAULT".
1146      * TODO(jrummell): Revisit this in the future if the security level gets
1147      * used for more things.
1148      */
1149     @CalledByNative
getSecurityLevel()1150     private String getSecurityLevel() {
1151         if (mMediaDrm == null || !isWidevine()) {
1152             Log.e(TAG, "getSecurityLevel(): MediaDrm is null or security level is not supported.");
1153             return "";
1154         }
1155 
1156         // Any failure in getPropertyString() means we don't know what the current security level
1157         // is.
1158         try {
1159             return mMediaDrm.getPropertyString(SECURITY_LEVEL);
1160         } catch (java.lang.IllegalStateException e) {
1161             // getPropertyString() may fail with android.media.MediaDrmResetException or
1162             // android.media.MediaDrm.MediaDrmStateException. As MediaDrmStateException was added in
1163             // API 21, we can't use it directly. However, both of these are IllegalStateExceptions,
1164             // so both will be handled here.
1165             Log.e(TAG, "Failed to get current security level", e);
1166             return "";
1167         } catch (Exception e) {
1168             // getPropertyString() has been failing with android.media.ResourceBusyException on some
1169             // devices. ResourceBusyException is not mentioned as a possible exception nor a runtime
1170             // exception and thus can not be listed, so catching all exceptions to handle it here.
1171             Log.e(TAG, "Failed to get current security level", e);
1172             return "";
1173         }
1174     }
1175 
1176     /**
1177      * Start provisioning. Returns true if a provisioning request can be
1178      * generated and has been forwarded to C++ code for handling, false
1179      * otherwise.
1180      */
startProvisioning()1181     private boolean startProvisioning() {
1182         Log.d(TAG, "startProvisioning");
1183         assert !mProvisioningPending;
1184         mProvisioningPending = true;
1185         assert mMediaDrm != null;
1186 
1187         if (!isNativeMediaDrmBridgeValid()) {
1188             return false;
1189         }
1190 
1191         if (mRequiresMediaCrypto) {
1192             sMediaCryptoDeferrer.onProvisionStarted();
1193         }
1194 
1195         // getProvisionRequest() may fail with android.media.MediaDrm.MediaDrmStateException or
1196         // android.media.MediaDrmResetException, both of which extend IllegalStateException. As
1197         // these specific exceptions are only available in API 21 and 23 respectively, using the
1198         // base exception so that this will work for all API versions.
1199         MediaDrm.ProvisionRequest request;
1200         try {
1201             request = mMediaDrm.getProvisionRequest();
1202         } catch (java.lang.IllegalStateException e) {
1203             Log.e(TAG, "Failed to get provisioning request", e);
1204             return false;
1205         }
1206 
1207         Log.i(TAG, "Provisioning origin ID %s", mOriginSet ? mOrigin : "<none>");
1208         MediaDrmBridgeJni.get().onProvisionRequest(mNativeMediaDrmBridge, MediaDrmBridge.this,
1209                 request.getDefaultUrl(), request.getData());
1210         return true;
1211     }
1212 
1213     /**
1214      * Called when the provision response is received.
1215      *
1216      * @param isResponseReceived Flag set to true if communication with
1217      * provision server was successful.
1218      * @param response Response data from the provision server.
1219      */
1220     @CalledByNative
processProvisionResponse(boolean isResponseReceived, byte[] response)1221     private void processProvisionResponse(boolean isResponseReceived, byte[] response) {
1222         Log.d(TAG, "processProvisionResponse()");
1223         assert mMediaCryptoSession == null;
1224 
1225         assert mProvisioningPending;
1226         mProvisioningPending = false;
1227 
1228         boolean success = false;
1229 
1230         // If |mMediaDrm| is released, there is no need to callback native.
1231         if (mMediaDrm != null) {
1232             success = isResponseReceived ? provideProvisionResponse(response) : false;
1233         }
1234 
1235         // This may call release() internally. However, sMediaCryptoDeferrer.onProvisionDone() will
1236         // still be called below to ensure provisioning failure here doesn't block other
1237         // MediaDrmBridge instances from proceeding.
1238         onProvisioned(success);
1239 
1240         if (mRequiresMediaCrypto) {
1241             sMediaCryptoDeferrer.onProvisionDone();
1242         }
1243     }
1244 
1245     /**
1246      * Provides the provision response to MediaDrm.
1247      *
1248      * @returns false if the response is invalid or on error, true otherwise.
1249      */
provideProvisionResponse(byte[] response)1250     boolean provideProvisionResponse(byte[] response) {
1251         if (response == null || response.length == 0) {
1252             Log.e(TAG, "Invalid provision response.");
1253             return false;
1254         }
1255 
1256         try {
1257             mMediaDrm.provideProvisionResponse(response);
1258             return true;
1259         } catch (android.media.DeniedByServerException e) {
1260             Log.e(TAG, "failed to provide provision response", e);
1261         } catch (java.lang.IllegalStateException e) {
1262             Log.e(TAG, "failed to provide provision response", e);
1263         }
1264         return false;
1265     }
1266 
1267     /*
1268      *  Provisioning complete. Continue to createMediaCrypto() if required.
1269      *
1270      * @param success Whether provisioning has succeeded or not.
1271      */
onProvisioned(boolean success)1272     void onProvisioned(boolean success) {
1273         if (!mRequiresMediaCrypto) {
1274             // No MediaCrypto required, so notify provisioning complete.
1275             MediaDrmBridgeJni.get().onProvisioningComplete(
1276                     mNativeMediaDrmBridge, MediaDrmBridge.this, success);
1277             if (!success) {
1278                 release();
1279             }
1280             return;
1281         }
1282 
1283         if (!success) {
1284             release();
1285             return;
1286         }
1287 
1288         if (!mOriginSet) {
1289             createMediaCrypto();
1290             return;
1291         }
1292 
1293         // When |mOriginSet|, notify the storage onProvisioned, and continue
1294         // creating MediaCrypto after that.
1295         mStorage.onProvisioned(new Callback<Boolean>() {
1296             @Override
1297             public void onResult(Boolean initSuccess) {
1298                 assert mMediaCryptoSession == null;
1299 
1300                 if (!initSuccess) {
1301                     Log.e(TAG, "Failed to initialize storage for origin");
1302                     release();
1303                     return;
1304                 }
1305 
1306                 createMediaCrypto();
1307             }
1308         });
1309     }
1310 
1311     /**
1312      * Delay session event handler if |mSessionEventDeferrer| exists and
1313      * matches |sessionId|. Otherwise run the handler immediately.
1314      */
deferEventHandleIfNeeded(SessionId sessionId, Runnable handler)1315     private void deferEventHandleIfNeeded(SessionId sessionId, Runnable handler) {
1316         if (mSessionEventDeferrer != null && mSessionEventDeferrer.shouldDefer(sessionId)) {
1317             mSessionEventDeferrer.defer(handler);
1318             return;
1319         }
1320 
1321         handler.run();
1322     }
1323 
1324     // Helper functions to make native calls.
1325 
onMediaCryptoReady(MediaCrypto mediaCrypto)1326     private void onMediaCryptoReady(MediaCrypto mediaCrypto) {
1327         if (isNativeMediaDrmBridgeValid()) {
1328             MediaDrmBridgeJni.get().onMediaCryptoReady(
1329                     mNativeMediaDrmBridge, MediaDrmBridge.this, mediaCrypto);
1330         }
1331     }
1332 
onPromiseResolved(final long promiseId)1333     private void onPromiseResolved(final long promiseId) {
1334         if (isNativeMediaDrmBridgeValid()) {
1335             MediaDrmBridgeJni.get().onPromiseResolved(
1336                     mNativeMediaDrmBridge, MediaDrmBridge.this, promiseId);
1337         }
1338     }
1339 
onPromiseResolvedWithSession(final long promiseId, final SessionId sessionId)1340     private void onPromiseResolvedWithSession(final long promiseId, final SessionId sessionId) {
1341         if (isNativeMediaDrmBridgeValid()) {
1342             MediaDrmBridgeJni.get().onPromiseResolvedWithSession(
1343                     mNativeMediaDrmBridge, MediaDrmBridge.this, promiseId, sessionId.emeId());
1344         }
1345     }
1346 
onPromiseRejected(final long promiseId, final String errorMessage)1347     private void onPromiseRejected(final long promiseId, final String errorMessage) {
1348         Log.e(TAG, "onPromiseRejected: %s", errorMessage);
1349         if (isNativeMediaDrmBridgeValid()) {
1350             MediaDrmBridgeJni.get().onPromiseRejected(
1351                     mNativeMediaDrmBridge, MediaDrmBridge.this, promiseId, errorMessage);
1352         }
1353     }
1354 
1355     @TargetApi(Build.VERSION_CODES.M)
onSessionMessage(final SessionId sessionId, final MediaDrm.KeyRequest request)1356     private void onSessionMessage(final SessionId sessionId, final MediaDrm.KeyRequest request) {
1357         if (!isNativeMediaDrmBridgeValid()) return;
1358 
1359         int requestType = MediaDrm.KeyRequest.REQUEST_TYPE_INITIAL;
1360         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
1361             requestType = request.getRequestType();
1362         } else {
1363             // Prior to M, getRequestType() is not supported. Do our best guess here: Assume
1364             // requests with a URL are renewals and all others are initial requests.
1365             requestType = request.getDefaultUrl().isEmpty()
1366                     ? MediaDrm.KeyRequest.REQUEST_TYPE_INITIAL
1367                     : MediaDrm.KeyRequest.REQUEST_TYPE_RENEWAL;
1368         }
1369 
1370         MediaDrmBridgeJni.get().onSessionMessage(mNativeMediaDrmBridge, MediaDrmBridge.this,
1371                 sessionId.emeId(), requestType, request.getData());
1372     }
1373 
onSessionClosed(final SessionId sessionId)1374     private void onSessionClosed(final SessionId sessionId) {
1375         if (isNativeMediaDrmBridgeValid()) {
1376             MediaDrmBridgeJni.get().onSessionClosed(
1377                     mNativeMediaDrmBridge, MediaDrmBridge.this, sessionId.emeId());
1378         }
1379     }
1380 
onSessionKeysChange(final SessionId sessionId, final Object[] keysInfo, final boolean hasAdditionalUsableKey, final boolean isKeyRelease)1381     private void onSessionKeysChange(final SessionId sessionId, final Object[] keysInfo,
1382             final boolean hasAdditionalUsableKey, final boolean isKeyRelease) {
1383         if (isNativeMediaDrmBridgeValid()) {
1384             MediaDrmBridgeJni.get().onSessionKeysChange(mNativeMediaDrmBridge, MediaDrmBridge.this,
1385                     sessionId.emeId(), keysInfo, hasAdditionalUsableKey, isKeyRelease);
1386         }
1387     }
1388 
onSessionExpirationUpdate(final SessionId sessionId, final long expirationTime)1389     private void onSessionExpirationUpdate(final SessionId sessionId, final long expirationTime) {
1390         if (isNativeMediaDrmBridgeValid()) {
1391             MediaDrmBridgeJni.get().onSessionExpirationUpdate(
1392                     mNativeMediaDrmBridge, MediaDrmBridge.this, sessionId.emeId(), expirationTime);
1393         }
1394     }
1395 
1396     @MainDex
1397     private class EventListener implements MediaDrm.OnEventListener {
1398         @Override
onEvent( MediaDrm mediaDrm, byte[] drmSessionId, int event, int extra, byte[] data)1399         public void onEvent(
1400                 MediaDrm mediaDrm, byte[] drmSessionId, int event, int extra, byte[] data) {
1401             if (drmSessionId == null) {
1402                 Log.e(TAG, "EventListener: No session for event %d.", event);
1403                 return;
1404             }
1405             SessionId sessionId = getSessionIdByDrmId(drmSessionId);
1406 
1407             if (sessionId == null) {
1408                 Log.e(TAG, "EventListener: Invalid session %s",
1409                         SessionId.toHexString(drmSessionId));
1410                 return;
1411             }
1412 
1413             SessionInfo sessionInfo = mSessionManager.get(sessionId);
1414             switch (event) {
1415                 case MediaDrm.EVENT_KEY_REQUIRED:
1416                     Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED");
1417                     MediaDrm.KeyRequest request = null;
1418                     try {
1419                         request = getKeyRequest(sessionId, data, sessionInfo.mimeType(),
1420                                 sessionInfo.keyType(), null);
1421                     } catch (android.media.NotProvisionedException e) {
1422                         Log.e(TAG, "Device not provisioned", e);
1423                         return;
1424                     }
1425                     if (request != null) {
1426                         onSessionMessage(sessionId, request);
1427                     } else {
1428                         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
1429                             onSessionKeysChange(sessionId,
1430                                     getDummyKeysInfo(MediaDrm.KeyStatus.STATUS_INTERNAL_ERROR)
1431                                             .toArray(),
1432                                     false, false);
1433                         }
1434                         Log.e(TAG, "EventListener: getKeyRequest failed.");
1435                         return;
1436                     }
1437                     break;
1438                 case MediaDrm.EVENT_KEY_EXPIRED:
1439                     Log.d(TAG, "MediaDrm.EVENT_KEY_EXPIRED");
1440                     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
1441                         onSessionKeysChange(sessionId,
1442                                 getDummyKeysInfo(MediaDrm.KeyStatus.STATUS_EXPIRED).toArray(),
1443                                 false, sessionInfo.keyType() == MediaDrm.KEY_TYPE_RELEASE);
1444                     }
1445                     break;
1446                 case MediaDrm.EVENT_VENDOR_DEFINED:
1447                     Log.d(TAG, "MediaDrm.EVENT_VENDOR_DEFINED");
1448                     assert false; // Should never happen.
1449                     break;
1450                 default:
1451                     Log.e(TAG, "Invalid DRM event " + event);
1452                     return;
1453             }
1454         }
1455     }
1456 
1457     @TargetApi(Build.VERSION_CODES.M)
1458     @MainDex
1459     private class KeyStatusChangeListener implements MediaDrm.OnKeyStatusChangeListener {
getKeysInfo(List<MediaDrm.KeyStatus> keyInformation)1460         private List<KeyStatus> getKeysInfo(List<MediaDrm.KeyStatus> keyInformation) {
1461             List<KeyStatus> keysInfo = new ArrayList<KeyStatus>();
1462             for (MediaDrm.KeyStatus keyStatus : keyInformation) {
1463                 keysInfo.add(new KeyStatus(keyStatus.getKeyId(), keyStatus.getStatusCode()));
1464             }
1465             return keysInfo;
1466         }
1467 
1468         @Override
onKeyStatusChange(MediaDrm md, byte[] drmSessionId, final List<MediaDrm.KeyStatus> keyInformation, final boolean hasNewUsableKey)1469         public void onKeyStatusChange(MediaDrm md, byte[] drmSessionId,
1470                 final List<MediaDrm.KeyStatus> keyInformation, final boolean hasNewUsableKey) {
1471             final SessionId sessionId = getSessionIdByDrmId(drmSessionId);
1472 
1473             assert sessionId != null;
1474             assert mSessionManager.get(sessionId) != null;
1475 
1476             final boolean isKeyRelease =
1477                     mSessionManager.get(sessionId).keyType() == MediaDrm.KEY_TYPE_RELEASE;
1478 
1479             deferEventHandleIfNeeded(sessionId, new Runnable() {
1480                 @Override
1481                 public void run() {
1482                     Log.d(TAG,
1483                             "KeysStatusChange: " + sessionId.toHexString() + ", "
1484                                     + hasNewUsableKey);
1485                     onSessionKeysChange(sessionId, getKeysInfo(keyInformation).toArray(),
1486                             hasNewUsableKey, isKeyRelease);
1487                 }
1488             });
1489         }
1490     }
1491 
1492     @TargetApi(Build.VERSION_CODES.M)
1493     @MainDex
1494     private class ExpirationUpdateListener implements MediaDrm.OnExpirationUpdateListener {
1495         @Override
onExpirationUpdate( MediaDrm md, byte[] drmSessionId, final long expirationTime)1496         public void onExpirationUpdate(
1497                 MediaDrm md, byte[] drmSessionId, final long expirationTime) {
1498             final SessionId sessionId = getSessionIdByDrmId(drmSessionId);
1499 
1500             assert sessionId != null;
1501 
1502             deferEventHandleIfNeeded(sessionId, new Runnable() {
1503                 @Override
1504                 public void run() {
1505                     Log.d(TAG,
1506                             "ExpirationUpdate: " + sessionId.toHexString() + ", " + expirationTime);
1507                     onSessionExpirationUpdate(sessionId, expirationTime);
1508                 }
1509             });
1510         }
1511     }
1512 
1513     @MainDex
1514     private class KeyUpdatedCallback implements Callback<Boolean> {
1515         private final SessionId mSessionId;
1516         private final long mPromiseId;
1517         private final boolean mIsKeyRelease;
1518 
KeyUpdatedCallback(SessionId sessionId, long promiseId, boolean isKeyRelease)1519         KeyUpdatedCallback(SessionId sessionId, long promiseId, boolean isKeyRelease) {
1520             mSessionId = sessionId;
1521             mPromiseId = promiseId;
1522             mIsKeyRelease = isKeyRelease;
1523         }
1524 
1525         @Override
onResult(Boolean success)1526         public void onResult(Boolean success) {
1527             if (!success) {
1528                 onPromiseRejected(mPromiseId, "failed to update key after response accepted");
1529                 return;
1530             }
1531 
1532             Log.d(TAG, "Key successfully %s for session %s", mIsKeyRelease ? "released" : "added",
1533                     mSessionId.toHexString());
1534             onPromiseResolved(mPromiseId);
1535 
1536             if (!mIsKeyRelease && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
1537                 onSessionKeysChange(mSessionId,
1538                         getDummyKeysInfo(MediaDrm.KeyStatus.STATUS_USABLE).toArray(), true,
1539                         mIsKeyRelease);
1540             }
1541         }
1542     }
1543 
1544     // At the native side, must post the task immediately to avoid reentrancy issues.
1545     @NativeMethods
1546     interface Natives {
onMediaCryptoReady( long nativeMediaDrmBridge, MediaDrmBridge caller, MediaCrypto mediaCrypto)1547         void onMediaCryptoReady(
1548                 long nativeMediaDrmBridge, MediaDrmBridge caller, MediaCrypto mediaCrypto);
1549 
onProvisionRequest(long nativeMediaDrmBridge, MediaDrmBridge caller, String defaultUrl, byte[] requestData)1550         void onProvisionRequest(long nativeMediaDrmBridge, MediaDrmBridge caller, String defaultUrl,
1551                 byte[] requestData);
onProvisioningComplete( long nativeMediaDrmBridge, MediaDrmBridge caller, boolean success)1552         void onProvisioningComplete(
1553                 long nativeMediaDrmBridge, MediaDrmBridge caller, boolean success);
1554 
onPromiseResolved(long nativeMediaDrmBridge, MediaDrmBridge caller, long promiseId)1555         void onPromiseResolved(long nativeMediaDrmBridge, MediaDrmBridge caller, long promiseId);
onPromiseResolvedWithSession(long nativeMediaDrmBridge, MediaDrmBridge caller, long promiseId, byte[] emeSessionId)1556         void onPromiseResolvedWithSession(long nativeMediaDrmBridge, MediaDrmBridge caller,
1557                 long promiseId, byte[] emeSessionId);
onPromiseRejected(long nativeMediaDrmBridge, MediaDrmBridge caller, long promiseId, String errorMessage)1558         void onPromiseRejected(long nativeMediaDrmBridge, MediaDrmBridge caller, long promiseId,
1559                 String errorMessage);
1560 
onSessionMessage(long nativeMediaDrmBridge, MediaDrmBridge caller, byte[] emeSessionId, int requestType, byte[] message)1561         void onSessionMessage(long nativeMediaDrmBridge, MediaDrmBridge caller, byte[] emeSessionId,
1562                 int requestType, byte[] message);
onSessionClosed(long nativeMediaDrmBridge, MediaDrmBridge caller, byte[] emeSessionId)1563         void onSessionClosed(long nativeMediaDrmBridge, MediaDrmBridge caller, byte[] emeSessionId);
onSessionKeysChange(long nativeMediaDrmBridge, MediaDrmBridge caller, byte[] emeSessionId, Object[] keysInfo, boolean hasAdditionalUsableKey, boolean isKeyRelease)1564         void onSessionKeysChange(long nativeMediaDrmBridge, MediaDrmBridge caller,
1565                 byte[] emeSessionId, Object[] keysInfo, boolean hasAdditionalUsableKey,
1566                 boolean isKeyRelease);
onSessionExpirationUpdate(long nativeMediaDrmBridge, MediaDrmBridge caller, byte[] emeSessionId, long expirationTime)1567         void onSessionExpirationUpdate(long nativeMediaDrmBridge, MediaDrmBridge caller,
1568                 byte[] emeSessionId, long expirationTime);
1569     }
1570 }
1571