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