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