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.media.MediaCrypto; 8 import android.media.MediaDrm; 9 import android.os.AsyncTask; 10 import android.os.Build; 11 import android.os.Handler; 12 import android.util.Log; 13 14 import org.apache.http.HttpResponse; 15 import org.apache.http.client.ClientProtocolException; 16 import org.apache.http.client.HttpClient; 17 import org.apache.http.client.methods.HttpPost; 18 import org.apache.http.impl.client.DefaultHttpClient; 19 import org.apache.http.util.EntityUtils; 20 import org.chromium.base.CalledByNative; 21 import org.chromium.base.JNINamespace; 22 23 import java.io.IOException; 24 import java.nio.ByteBuffer; 25 import java.nio.ByteOrder; 26 import java.util.ArrayDeque; 27 import java.util.HashMap; 28 import java.util.UUID; 29 30 /** 31 * A wrapper of the android MediaDrm class. Each MediaDrmBridge manages multiple 32 * sessions for a single MediaSourcePlayer. 33 */ 34 @JNINamespace("media") 35 public class MediaDrmBridge { 36 // Implementation Notes: 37 // - A media crypto session (mMediaCryptoSession) is opened after MediaDrm 38 // is created. This session will be added to mSessionIds. 39 // a) In multiple session mode, this session will only be used to create 40 // the MediaCrypto object. It's associated mime type is always null and 41 // it's session ID is always INVALID_SESSION_ID. 42 // b) In single session mode, this session will be used to create the 43 // MediaCrypto object and will be used to call getKeyRequest() and 44 // manage all keys. The session ID will always be the lastest session 45 // ID passed by the caller. 46 // - Each createSession() call creates a new session. All sessions are 47 // managed in mSessionIds. 48 // - Whenever NotProvisionedException is thrown, we will clean up the 49 // current state and start the provisioning process. 50 // - When provisioning is finished, we will try to resume suspended 51 // operations: 52 // a) Create the media crypto session if it's not created. 53 // b) Finish createSession() if previous createSession() was interrupted 54 // by a NotProvisionedException. 55 // - Whenever an unexpected error occurred, we'll call release() to release 56 // all resources and clear all states. In that case all calls to this 57 // object will be no-op. All public APIs and callbacks should check 58 // mMediaBridge to make sure release() hasn't been called. Also, we call 59 // release() immediately after the error happens (e.g. after mMediaDrm) 60 // calls. Indirect calls should not call release() again to avoid 61 // duplication (even though it doesn't hurt to call release() twice). 62 63 private static final String TAG = "MediaDrmBridge"; 64 private static final String SECURITY_LEVEL = "securityLevel"; 65 private static final String PRIVACY_MODE = "privacyMode"; 66 private static final String SESSION_SHARING = "sessionSharing"; 67 private static final String ENABLE = "enable"; 68 private static final int INVALID_SESSION_ID = 0; 69 70 private MediaDrm mMediaDrm; 71 private long mNativeMediaDrmBridge; 72 private UUID mSchemeUUID; 73 private Handler mHandler; 74 75 // In this mode, we only open one session, i.e. mMediaCryptoSession. 76 private boolean mSingleSessionMode; 77 78 // A session only for the purpose of creating a MediaCrypto object. 79 // This session is opened when createSession() is called for the first 80 // time. 81 // - In multiple session mode, all following createSession() calls 82 // should create a new session and use it to call getKeyRequest(). No 83 // getKeyRequest() should ever be called on this media crypto session. 84 // - In single session mode, all createSession() calls use the same 85 // media crypto session. When createSession() is called with a new 86 // initData, previously added keys may not be available anymore. 87 private ByteBuffer mMediaCryptoSession; 88 private MediaCrypto mMediaCrypto; 89 90 // The map of all opened sessions to their session reference IDs. 91 private HashMap<ByteBuffer, Integer> mSessionIds; 92 // The map of all opened sessions to their mime types. 93 private HashMap<ByteBuffer, String> mSessionMimeTypes; 94 95 // The queue of all pending createSession() data. 96 private ArrayDeque<PendingCreateSessionData> mPendingCreateSessionDataQueue; 97 98 private boolean mResetDeviceCredentialsPending; 99 100 // MediaDrmBridge is waiting for provisioning response from the server. 101 // 102 // Notes about NotProvisionedException: This exception can be thrown in a 103 // lot of cases. To streamline implementation, we do not catch it in private 104 // non-native methods and only catch it in public APIs. 105 private boolean mProvisioningPending; 106 107 /** 108 * This class contains data needed to call createSession(). 109 */ 110 private static class PendingCreateSessionData { 111 private final int mSessionId; 112 private final byte[] mInitData; 113 private final String mMimeType; 114 PendingCreateSessionData(int sessionId, byte[] initData, String mimeType)115 private PendingCreateSessionData(int sessionId, byte[] initData, String mimeType) { 116 mSessionId = sessionId; 117 mInitData = initData; 118 mMimeType = mimeType; 119 } 120 sessionId()121 private int sessionId() { return mSessionId; } initData()122 private byte[] initData() { return mInitData; } mimeType()123 private String mimeType() { return mMimeType; } 124 } 125 getUUIDFromBytes(byte[] data)126 private static UUID getUUIDFromBytes(byte[] data) { 127 if (data.length != 16) { 128 return null; 129 } 130 long mostSigBits = 0; 131 long leastSigBits = 0; 132 for (int i = 0; i < 8; i++) { 133 mostSigBits = (mostSigBits << 8) | (data[i] & 0xff); 134 } 135 for (int i = 8; i < 16; i++) { 136 leastSigBits = (leastSigBits << 8) | (data[i] & 0xff); 137 } 138 return new UUID(mostSigBits, leastSigBits); 139 } 140 141 /** 142 * Gets session associated with the sessionId. 143 * 144 * @return session if sessionId maps a valid opened session. Returns null 145 * otherwise. 146 */ getSession(int sessionId)147 private ByteBuffer getSession(int sessionId) { 148 for (ByteBuffer session : mSessionIds.keySet()) { 149 if (mSessionIds.get(session) == sessionId) { 150 return session; 151 } 152 } 153 return null; 154 } 155 MediaDrmBridge(UUID schemeUUID, long nativeMediaDrmBridge, boolean singleSessionMode)156 private MediaDrmBridge(UUID schemeUUID, long nativeMediaDrmBridge, boolean singleSessionMode) 157 throws android.media.UnsupportedSchemeException { 158 mSchemeUUID = schemeUUID; 159 mMediaDrm = new MediaDrm(schemeUUID); 160 mNativeMediaDrmBridge = nativeMediaDrmBridge; 161 mHandler = new Handler(); 162 mSingleSessionMode = singleSessionMode; 163 mSessionIds = new HashMap<ByteBuffer, Integer>(); 164 mSessionMimeTypes = new HashMap<ByteBuffer, String>(); 165 mPendingCreateSessionDataQueue = new ArrayDeque<PendingCreateSessionData>(); 166 mResetDeviceCredentialsPending = false; 167 mProvisioningPending = false; 168 169 mMediaDrm.setOnEventListener(new MediaDrmListener()); 170 mMediaDrm.setPropertyString(PRIVACY_MODE, ENABLE); 171 if (!mSingleSessionMode) { 172 mMediaDrm.setPropertyString(SESSION_SHARING, ENABLE); 173 } 174 175 // We could open a MediaCrypto session here to support faster start of 176 // clear lead (no need to wait for createSession()). But on 177 // Android, memory and battery resources are precious and we should 178 // only create a session when we are sure we'll use it. 179 // TODO(xhwang): Investigate other options to support fast start. 180 } 181 182 /** 183 * Create a MediaCrypto object. 184 * 185 * @return whether a MediaCrypto object is successfully created. 186 */ createMediaCrypto()187 private boolean createMediaCrypto() throws android.media.NotProvisionedException { 188 if (mMediaDrm == null) { 189 return false; 190 } 191 assert !mProvisioningPending; 192 assert mMediaCryptoSession == null; 193 assert mMediaCrypto == null; 194 195 // Open media crypto session. 196 mMediaCryptoSession = openSession(); 197 if (mMediaCryptoSession == null) { 198 Log.e(TAG, "Cannot create MediaCrypto Session."); 199 return false; 200 } 201 Log.d(TAG, "MediaCrypto Session created: " + mMediaCryptoSession); 202 203 // Create MediaCrypto object. 204 try { 205 if (MediaCrypto.isCryptoSchemeSupported(mSchemeUUID)) { 206 final byte[] mediaCryptoSession = mMediaCryptoSession.array(); 207 mMediaCrypto = new MediaCrypto(mSchemeUUID, mediaCryptoSession); 208 assert mMediaCrypto != null; 209 Log.d(TAG, "MediaCrypto successfully created!"); 210 mSessionIds.put(mMediaCryptoSession, INVALID_SESSION_ID); 211 // Notify the native code that MediaCrypto is ready. 212 nativeOnMediaCryptoReady(mNativeMediaDrmBridge); 213 return true; 214 } else { 215 Log.e(TAG, "Cannot create MediaCrypto for unsupported scheme."); 216 } 217 } catch (android.media.MediaCryptoException e) { 218 Log.e(TAG, "Cannot create MediaCrypto", e); 219 } 220 221 release(); 222 return false; 223 } 224 225 /** 226 * Open a new session.. 227 * 228 * @return the session opened. Returns null if unexpected error happened. 229 */ openSession()230 private ByteBuffer openSession() throws android.media.NotProvisionedException { 231 assert mMediaDrm != null; 232 try { 233 byte[] session = mMediaDrm.openSession(); 234 // ByteBuffer.wrap() is backed by the byte[]. Make a clone here in 235 // case the underlying byte[] is modified. 236 return ByteBuffer.wrap(session.clone()); 237 } catch (java.lang.RuntimeException e) { // TODO(xhwang): Drop this? 238 Log.e(TAG, "Cannot open a new session", e); 239 release(); 240 return null; 241 } catch (android.media.NotProvisionedException e) { 242 // Throw NotProvisionedException so that we can startProvisioning(). 243 throw e; 244 } catch (android.media.MediaDrmException e) { 245 // Other MediaDrmExceptions (e.g. ResourceBusyException) are not 246 // recoverable. 247 Log.e(TAG, "Cannot open a new session", e); 248 release(); 249 return null; 250 } 251 } 252 253 /** 254 * Close a session. 255 * 256 * @param session to be closed. 257 */ closeSession(ByteBuffer session)258 private void closeSession(ByteBuffer session) { 259 assert mMediaDrm != null; 260 mMediaDrm.closeSession(session.array()); 261 } 262 263 /** 264 * Check whether the crypto scheme is supported for the given container. 265 * If |containerMimeType| is an empty string, we just return whether 266 * the crypto scheme is supported. 267 * 268 * @return true if the container and the crypto scheme is supported, or 269 * false otherwise. 270 */ 271 @CalledByNative isCryptoSchemeSupported(byte[] schemeUUID, String containerMimeType)272 private static boolean isCryptoSchemeSupported(byte[] schemeUUID, String containerMimeType) { 273 UUID cryptoScheme = getUUIDFromBytes(schemeUUID); 274 275 if (containerMimeType.isEmpty()) { 276 return MediaDrm.isCryptoSchemeSupported(cryptoScheme); 277 } 278 279 return MediaDrm.isCryptoSchemeSupported(cryptoScheme, containerMimeType); 280 } 281 282 /** 283 * Create a new MediaDrmBridge from the crypto scheme UUID. 284 * 285 * @param schemeUUID Crypto scheme UUID. 286 * @param securityLevel Security level to be used. 287 * @param nativeMediaDrmBridge Native object of this class. 288 */ 289 @CalledByNative create(byte[] schemeUUID, long nativeMediaDrmBridge)290 private static MediaDrmBridge create(byte[] schemeUUID, long nativeMediaDrmBridge) { 291 UUID cryptoScheme = getUUIDFromBytes(schemeUUID); 292 if (cryptoScheme == null || !MediaDrm.isCryptoSchemeSupported(cryptoScheme)) { 293 return null; 294 } 295 296 boolean singleSessionMode = false; 297 if (Build.VERSION.RELEASE.equals("4.4")) { 298 singleSessionMode = true; 299 } 300 Log.d(TAG, "MediaDrmBridge uses " + 301 (singleSessionMode ? "single" : "multiple") + "-session mode."); 302 303 MediaDrmBridge mediaDrmBridge = null; 304 try { 305 mediaDrmBridge = new MediaDrmBridge( 306 cryptoScheme, nativeMediaDrmBridge, singleSessionMode); 307 Log.d(TAG, "MediaDrmBridge successfully created."); 308 } catch (android.media.UnsupportedSchemeException e) { 309 Log.e(TAG, "Unsupported DRM scheme", e); 310 } catch (java.lang.IllegalArgumentException e) { 311 Log.e(TAG, "Failed to create MediaDrmBridge", e); 312 } catch (java.lang.IllegalStateException e) { 313 Log.e(TAG, "Failed to create MediaDrmBridge", e); 314 } 315 316 return mediaDrmBridge; 317 } 318 319 /** 320 * Set the security level that the MediaDrm object uses. 321 * This function should be called right after we construct MediaDrmBridge 322 * and before we make any other calls. 323 */ 324 @CalledByNative setSecurityLevel(String securityLevel)325 private boolean setSecurityLevel(String securityLevel) { 326 if (mMediaDrm == null || mMediaCrypto != null) { 327 return false; 328 } 329 330 String currentSecurityLevel = mMediaDrm.getPropertyString(SECURITY_LEVEL); 331 Log.e(TAG, "Security level: current " + currentSecurityLevel + ", new " + securityLevel); 332 if (securityLevel.equals(currentSecurityLevel)) { 333 // No need to set the same security level again. This is not just 334 // a shortcut! Setting the same security level actually causes an 335 // exception in MediaDrm! 336 return true; 337 } 338 339 try { 340 mMediaDrm.setPropertyString(SECURITY_LEVEL, securityLevel); 341 return true; 342 } catch (java.lang.IllegalArgumentException e) { 343 Log.e(TAG, "Failed to set security level " + securityLevel, e); 344 } catch (java.lang.IllegalStateException e) { 345 Log.e(TAG, "Failed to set security level " + securityLevel, e); 346 } 347 348 Log.e(TAG, "Security level " + securityLevel + " not supported!"); 349 return false; 350 } 351 352 /** 353 * Return the MediaCrypto object if available. 354 */ 355 @CalledByNative getMediaCrypto()356 private MediaCrypto getMediaCrypto() { 357 return mMediaCrypto; 358 } 359 360 /** 361 * Reset the device DRM credentials. 362 */ 363 @CalledByNative resetDeviceCredentials()364 private void resetDeviceCredentials() { 365 mResetDeviceCredentialsPending = true; 366 MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest(); 367 PostRequestTask postTask = new PostRequestTask(request.getData()); 368 postTask.execute(request.getDefaultUrl()); 369 } 370 371 /** 372 * Release the MediaDrmBridge object. 373 */ 374 @CalledByNative release()375 private void release() { 376 // Do not reset mHandler and mNativeMediaDrmBridge so that we can still 377 // post KeyError back to native code. 378 379 mPendingCreateSessionDataQueue.clear(); 380 mPendingCreateSessionDataQueue = null; 381 382 for (ByteBuffer session : mSessionIds.keySet()) { 383 closeSession(session); 384 } 385 mSessionIds.clear(); 386 mSessionIds = null; 387 mSessionMimeTypes.clear(); 388 mSessionMimeTypes = null; 389 390 // This session was closed in the "for" loop above. 391 mMediaCryptoSession = null; 392 393 if (mMediaCrypto != null) { 394 mMediaCrypto.release(); 395 mMediaCrypto = null; 396 } 397 398 if (mMediaDrm != null) { 399 mMediaDrm.release(); 400 mMediaDrm = null; 401 } 402 } 403 404 /** 405 * Get a key request. 406 * 407 * @param session Session on which we need to get the key request. 408 * @param data Data needed to get the key request. 409 * @param mime Mime type to get the key request. 410 * 411 * @return the key request. 412 */ getKeyRequest(ByteBuffer session, byte[] data, String mime)413 private MediaDrm.KeyRequest getKeyRequest(ByteBuffer session, byte[] data, String mime) 414 throws android.media.NotProvisionedException { 415 assert mMediaDrm != null; 416 assert mMediaCrypto != null; 417 assert !mProvisioningPending; 418 419 HashMap<String, String> optionalParameters = new HashMap<String, String>(); 420 MediaDrm.KeyRequest request = mMediaDrm.getKeyRequest( 421 session.array(), data, mime, MediaDrm.KEY_TYPE_STREAMING, optionalParameters); 422 String result = (request != null) ? "successed" : "failed"; 423 Log.d(TAG, "getKeyRequest " + result + "!"); 424 return request; 425 } 426 427 /** 428 * Save data to |mPendingCreateSessionDataQueue| so that we can resume the 429 * createSession() call later. 430 */ savePendingCreateSessionData(int sessionId, byte[] initData, String mime)431 private void savePendingCreateSessionData(int sessionId, byte[] initData, String mime) { 432 Log.d(TAG, "savePendingCreateSessionData()"); 433 mPendingCreateSessionDataQueue.offer( 434 new PendingCreateSessionData(sessionId, initData, mime)); 435 } 436 437 /** 438 * Process all pending createSession() calls synchronously. 439 */ processPendingCreateSessionData()440 private void processPendingCreateSessionData() { 441 Log.d(TAG, "processPendingCreateSessionData()"); 442 assert mMediaDrm != null; 443 444 // Check mMediaDrm != null because error may happen in createSession(). 445 // Check !mProvisioningPending because NotProvisionedException may be 446 // thrown in createSession(). 447 while (mMediaDrm != null && !mProvisioningPending && 448 !mPendingCreateSessionDataQueue.isEmpty()) { 449 PendingCreateSessionData pendingData = mPendingCreateSessionDataQueue.poll(); 450 int sessionId = pendingData.sessionId(); 451 byte[] initData = pendingData.initData(); 452 String mime = pendingData.mimeType(); 453 createSession(sessionId, initData, mime); 454 } 455 } 456 457 /** 458 * Process pending operations asynchrnously. 459 */ resumePendingOperations()460 private void resumePendingOperations() { 461 mHandler.post(new Runnable(){ 462 @Override 463 public void run() { 464 processPendingCreateSessionData(); 465 } 466 }); 467 } 468 469 /** 470 * Create a session with |sessionId|, |initData| and |mime|. 471 * In multiple session mode, a new session will be open. In single session 472 * mode, the mMediaCryptoSession will be used. 473 * 474 * @param sessionId ID for the session to be created. 475 * @param initData Data needed to generate the key request. 476 * @param mime Mime type. 477 */ 478 @CalledByNative createSession(int sessionId, byte[] initData, String mime)479 private void createSession(int sessionId, byte[] initData, String mime) { 480 Log.d(TAG, "createSession()"); 481 if (mMediaDrm == null) { 482 Log.e(TAG, "createSession() called when MediaDrm is null."); 483 return; 484 } 485 486 if (mProvisioningPending) { 487 assert mMediaCrypto == null; 488 savePendingCreateSessionData(sessionId, initData, mime); 489 return; 490 } 491 492 boolean newSessionOpened = false; 493 ByteBuffer session = null; 494 try { 495 // Create MediaCrypto if necessary. 496 if (mMediaCrypto == null && !createMediaCrypto()) { 497 onSessionError(sessionId); 498 return; 499 } 500 assert mMediaCrypto != null; 501 assert mSessionIds.containsKey(mMediaCryptoSession); 502 503 if (mSingleSessionMode) { 504 session = mMediaCryptoSession; 505 if (mSessionMimeTypes.get(session) != null && 506 !mSessionMimeTypes.get(session).equals(mime)) { 507 Log.e(TAG, "Only one mime type is supported in single session mode."); 508 onSessionError(sessionId); 509 return; 510 } 511 } else { 512 session = openSession(); 513 if (session == null) { 514 Log.e(TAG, "Cannot open session in createSession()."); 515 onSessionError(sessionId); 516 return; 517 } 518 newSessionOpened = true; 519 assert !mSessionIds.containsKey(session); 520 } 521 522 MediaDrm.KeyRequest request = null; 523 request = getKeyRequest(session, initData, mime); 524 if (request == null) { 525 if (newSessionOpened) { 526 closeSession(session); 527 } 528 onSessionError(sessionId); 529 return; 530 } 531 532 onSessionCreated(sessionId, getWebSessionId(session)); 533 onSessionMessage(sessionId, request); 534 if (newSessionOpened) { 535 Log.d(TAG, "createSession(): Session " + getWebSessionId(session) + 536 " (" + sessionId + ") created."); 537 } 538 539 mSessionIds.put(session, sessionId); 540 mSessionMimeTypes.put(session, mime); 541 } catch (android.media.NotProvisionedException e) { 542 Log.e(TAG, "Device not provisioned", e); 543 if (newSessionOpened) { 544 closeSession(session); 545 } 546 savePendingCreateSessionData(sessionId, initData, mime); 547 startProvisioning(); 548 } 549 } 550 551 /** 552 * Returns whether |sessionId| is a valid key session, excluding the media 553 * crypto session in multi-session mode. 554 * 555 * @param sessionId Crypto session Id. 556 */ sessionExists(ByteBuffer session)557 private boolean sessionExists(ByteBuffer session) { 558 if (mMediaCryptoSession == null) { 559 assert mSessionIds.isEmpty(); 560 Log.e(TAG, "Session doesn't exist because media crypto session is not created."); 561 return false; 562 } 563 assert mSessionIds.containsKey(mMediaCryptoSession); 564 565 if (mSingleSessionMode) { 566 return mMediaCryptoSession.equals(session); 567 } 568 569 return !session.equals(mMediaCryptoSession) && mSessionIds.containsKey(session); 570 } 571 572 /** 573 * Cancel a key request for a session Id. 574 * 575 * @param sessionId Reference ID of session to be released. 576 */ 577 @CalledByNative releaseSession(int sessionId)578 private void releaseSession(int sessionId) { 579 Log.d(TAG, "releaseSession(): " + sessionId); 580 if (mMediaDrm == null) { 581 Log.e(TAG, "releaseSession() called when MediaDrm is null."); 582 return; 583 } 584 585 ByteBuffer session = getSession(sessionId); 586 if (session == null) { 587 Log.e(TAG, "Invalid sessionId in releaseSession."); 588 onSessionError(sessionId); 589 return; 590 } 591 592 mMediaDrm.removeKeys(session.array()); 593 594 // We don't close the media crypto session in single session mode. 595 if (!mSingleSessionMode) { 596 Log.d(TAG, "Session " + sessionId + "closed."); 597 closeSession(session); 598 mSessionIds.remove(session); 599 onSessionClosed(sessionId); 600 } 601 } 602 603 /** 604 * Add a key for a session Id. 605 * 606 * @param sessionId Reference ID of session to be updated. 607 * @param key Response data from the server. 608 */ 609 @CalledByNative updateSession(int sessionId, byte[] key)610 private void updateSession(int sessionId, byte[] key) { 611 Log.d(TAG, "updateSession(): " + sessionId); 612 if (mMediaDrm == null) { 613 Log.e(TAG, "updateSession() called when MediaDrm is null."); 614 return; 615 } 616 617 // TODO(xhwang): We should be able to DCHECK this when WD EME is implemented. 618 ByteBuffer session = getSession(sessionId); 619 if (!sessionExists(session)) { 620 Log.e(TAG, "Invalid session in updateSession."); 621 onSessionError(sessionId); 622 return; 623 } 624 625 try { 626 try { 627 mMediaDrm.provideKeyResponse(session.array(), key); 628 } catch (java.lang.IllegalStateException e) { 629 // This is not really an exception. Some error code are incorrectly 630 // reported as an exception. 631 // TODO(qinmin): remove this exception catch when b/10495563 is fixed. 632 Log.e(TAG, "Exception intentionally caught when calling provideKeyResponse()", e); 633 } 634 onSessionReady(sessionId); 635 Log.d(TAG, "Key successfully added for session " + sessionId); 636 return; 637 } catch (android.media.NotProvisionedException e) { 638 // TODO(xhwang): Should we handle this? 639 Log.e(TAG, "failed to provide key response", e); 640 } catch (android.media.DeniedByServerException e) { 641 Log.e(TAG, "failed to provide key response", e); 642 } 643 onSessionError(sessionId); 644 release(); 645 } 646 647 /** 648 * Return the security level of this DRM object. 649 */ 650 @CalledByNative getSecurityLevel()651 private String getSecurityLevel() { 652 if (mMediaDrm == null) { 653 Log.e(TAG, "getSecurityLevel() called when MediaDrm is null."); 654 return null; 655 } 656 return mMediaDrm.getPropertyString("securityLevel"); 657 } 658 startProvisioning()659 private void startProvisioning() { 660 Log.d(TAG, "startProvisioning"); 661 assert mMediaDrm != null; 662 assert !mProvisioningPending; 663 mProvisioningPending = true; 664 MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest(); 665 PostRequestTask postTask = new PostRequestTask(request.getData()); 666 postTask.execute(request.getDefaultUrl()); 667 } 668 669 /** 670 * Called when the provision response is received. 671 * 672 * @param response Response data from the provision server. 673 */ onProvisionResponse(byte[] response)674 private void onProvisionResponse(byte[] response) { 675 Log.d(TAG, "onProvisionResponse()"); 676 assert mProvisioningPending; 677 mProvisioningPending = false; 678 679 // If |mMediaDrm| is released, there is no need to callback native. 680 if (mMediaDrm == null) { 681 return; 682 } 683 684 boolean success = provideProvisionResponse(response); 685 686 if (mResetDeviceCredentialsPending) { 687 nativeOnResetDeviceCredentialsCompleted(mNativeMediaDrmBridge, success); 688 mResetDeviceCredentialsPending = false; 689 } 690 691 if (success) { 692 resumePendingOperations(); 693 } 694 } 695 696 /** 697 * Provide the provisioning response to MediaDrm. 698 * @returns false if the response is invalid or on error, true otherwise. 699 */ provideProvisionResponse(byte[] response)700 boolean provideProvisionResponse(byte[] response) { 701 if (response == null || response.length == 0) { 702 Log.e(TAG, "Invalid provision response."); 703 return false; 704 } 705 706 try { 707 mMediaDrm.provideProvisionResponse(response); 708 return true; 709 } catch (android.media.DeniedByServerException e) { 710 Log.e(TAG, "failed to provide provision response", e); 711 } catch (java.lang.IllegalStateException e) { 712 Log.e(TAG, "failed to provide provision response", e); 713 } 714 return false; 715 } 716 onSessionCreated(final int sessionId, final String webSessionId)717 private void onSessionCreated(final int sessionId, final String webSessionId) { 718 mHandler.post(new Runnable(){ 719 @Override 720 public void run() { 721 nativeOnSessionCreated(mNativeMediaDrmBridge, sessionId, webSessionId); 722 } 723 }); 724 } 725 onSessionMessage(final int sessionId, final MediaDrm.KeyRequest request)726 private void onSessionMessage(final int sessionId, final MediaDrm.KeyRequest request) { 727 mHandler.post(new Runnable(){ 728 @Override 729 public void run() { 730 nativeOnSessionMessage(mNativeMediaDrmBridge, sessionId, 731 request.getData(), request.getDefaultUrl()); 732 } 733 }); 734 } 735 onSessionReady(final int sessionId)736 private void onSessionReady(final int sessionId) { 737 mHandler.post(new Runnable() { 738 @Override 739 public void run() { 740 nativeOnSessionReady(mNativeMediaDrmBridge, sessionId); 741 } 742 }); 743 } 744 onSessionClosed(final int sessionId)745 private void onSessionClosed(final int sessionId) { 746 mHandler.post(new Runnable() { 747 @Override 748 public void run() { 749 nativeOnSessionClosed(mNativeMediaDrmBridge, sessionId); 750 } 751 }); 752 } 753 onSessionError(final int sessionId)754 private void onSessionError(final int sessionId) { 755 // TODO(qinmin): pass the error code to native. 756 mHandler.post(new Runnable() { 757 @Override 758 public void run() { 759 nativeOnSessionError(mNativeMediaDrmBridge, sessionId); 760 } 761 }); 762 } 763 getWebSessionId(ByteBuffer session)764 private String getWebSessionId(ByteBuffer session) { 765 String webSessionId = null; 766 try { 767 webSessionId = new String(session.array(), "UTF-8"); 768 } catch (java.io.UnsupportedEncodingException e) { 769 Log.e(TAG, "getWebSessionId failed", e); 770 } catch (java.lang.NullPointerException e) { 771 Log.e(TAG, "getWebSessionId failed", e); 772 } 773 return webSessionId; 774 } 775 776 private class MediaDrmListener implements MediaDrm.OnEventListener { 777 @Override onEvent( MediaDrm mediaDrm, byte[] session_array, int event, int extra, byte[] data)778 public void onEvent( 779 MediaDrm mediaDrm, byte[] session_array, int event, int extra, byte[] data) { 780 if (session_array == null) { 781 Log.e(TAG, "MediaDrmListener: Null session."); 782 return; 783 } 784 ByteBuffer session = ByteBuffer.wrap(session_array); 785 if (!sessionExists(session)) { 786 Log.e(TAG, "MediaDrmListener: Invalid session."); 787 return; 788 } 789 Integer sessionId = mSessionIds.get(session); 790 if (sessionId == null || sessionId == INVALID_SESSION_ID) { 791 Log.e(TAG, "MediaDrmListener: Invalid session ID."); 792 return; 793 } 794 switch(event) { 795 case MediaDrm.EVENT_PROVISION_REQUIRED: 796 Log.d(TAG, "MediaDrm.EVENT_PROVISION_REQUIRED"); 797 break; 798 case MediaDrm.EVENT_KEY_REQUIRED: 799 Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED"); 800 if (mProvisioningPending) { 801 return; 802 } 803 String mime = mSessionMimeTypes.get(session); 804 MediaDrm.KeyRequest request = null; 805 try { 806 request = getKeyRequest(session, data, mime); 807 } catch (android.media.NotProvisionedException e) { 808 Log.e(TAG, "Device not provisioned", e); 809 startProvisioning(); 810 return; 811 } 812 if (request != null) { 813 onSessionMessage(sessionId, request); 814 } else { 815 onSessionError(sessionId); 816 } 817 break; 818 case MediaDrm.EVENT_KEY_EXPIRED: 819 Log.d(TAG, "MediaDrm.EVENT_KEY_EXPIRED"); 820 onSessionError(sessionId); 821 break; 822 case MediaDrm.EVENT_VENDOR_DEFINED: 823 Log.d(TAG, "MediaDrm.EVENT_VENDOR_DEFINED"); 824 assert false; // Should never happen. 825 break; 826 default: 827 Log.e(TAG, "Invalid DRM event " + event); 828 return; 829 } 830 } 831 } 832 833 private class PostRequestTask extends AsyncTask<String, Void, Void> { 834 private static final String TAG = "PostRequestTask"; 835 836 private byte[] mDrmRequest; 837 private byte[] mResponseBody; 838 PostRequestTask(byte[] drmRequest)839 public PostRequestTask(byte[] drmRequest) { 840 mDrmRequest = drmRequest; 841 } 842 843 @Override doInBackground(String... urls)844 protected Void doInBackground(String... urls) { 845 mResponseBody = postRequest(urls[0], mDrmRequest); 846 if (mResponseBody != null) { 847 Log.d(TAG, "response length=" + mResponseBody.length); 848 } 849 return null; 850 } 851 postRequest(String url, byte[] drmRequest)852 private byte[] postRequest(String url, byte[] drmRequest) { 853 HttpClient httpClient = new DefaultHttpClient(); 854 HttpPost httpPost = new HttpPost(url + "&signedRequest=" + new String(drmRequest)); 855 856 Log.d(TAG, "PostRequest:" + httpPost.getRequestLine()); 857 try { 858 // Add data 859 httpPost.setHeader("Accept", "*/*"); 860 httpPost.setHeader("User-Agent", "Widevine CDM v1.0"); 861 httpPost.setHeader("Content-Type", "application/json"); 862 863 // Execute HTTP Post Request 864 HttpResponse response = httpClient.execute(httpPost); 865 866 byte[] responseBody; 867 int responseCode = response.getStatusLine().getStatusCode(); 868 if (responseCode == 200) { 869 responseBody = EntityUtils.toByteArray(response.getEntity()); 870 } else { 871 Log.d(TAG, "Server returned HTTP error code " + responseCode); 872 return null; 873 } 874 return responseBody; 875 } catch (ClientProtocolException e) { 876 e.printStackTrace(); 877 } catch (IOException e) { 878 e.printStackTrace(); 879 } 880 return null; 881 } 882 883 @Override onPostExecute(Void v)884 protected void onPostExecute(Void v) { 885 onProvisionResponse(mResponseBody); 886 } 887 } 888 addKeySystemUuidMapping(String keySystem, UUID uuid)889 public static void addKeySystemUuidMapping(String keySystem, UUID uuid) { 890 ByteBuffer uuidBuffer = ByteBuffer.allocateDirect(16); 891 // MSB (byte) should be positioned at the first element. 892 uuidBuffer.order(ByteOrder.BIG_ENDIAN); 893 uuidBuffer.putLong(uuid.getMostSignificantBits()); 894 uuidBuffer.putLong(uuid.getLeastSignificantBits()); 895 nativeAddKeySystemUuidMapping(keySystem, uuidBuffer); 896 } 897 nativeOnMediaCryptoReady(long nativeMediaDrmBridge)898 private native void nativeOnMediaCryptoReady(long nativeMediaDrmBridge); 899 nativeOnSessionCreated(long nativeMediaDrmBridge, int sessionId, String webSessionId)900 private native void nativeOnSessionCreated(long nativeMediaDrmBridge, int sessionId, 901 String webSessionId); 902 nativeOnSessionMessage(long nativeMediaDrmBridge, int sessionId, byte[] message, String destinationUrl)903 private native void nativeOnSessionMessage(long nativeMediaDrmBridge, int sessionId, 904 byte[] message, String destinationUrl); 905 nativeOnSessionReady(long nativeMediaDrmBridge, int sessionId)906 private native void nativeOnSessionReady(long nativeMediaDrmBridge, int sessionId); 907 nativeOnSessionClosed(long nativeMediaDrmBridge, int sessionId)908 private native void nativeOnSessionClosed(long nativeMediaDrmBridge, int sessionId); 909 nativeOnSessionError(long nativeMediaDrmBridge, int sessionId)910 private native void nativeOnSessionError(long nativeMediaDrmBridge, int sessionId); 911 nativeOnResetDeviceCredentialsCompleted( long nativeMediaDrmBridge, boolean success)912 private native void nativeOnResetDeviceCredentialsCompleted( 913 long nativeMediaDrmBridge, boolean success); 914 nativeAddKeySystemUuidMapping(String keySystem, ByteBuffer uuid)915 private static native void nativeAddKeySystemUuidMapping(String keySystem, ByteBuffer uuid); 916 } 917