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.nio.ByteOrder; 26import java.util.ArrayDeque; 27import java.util.HashMap; 28import 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") 35public 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 115 private PendingCreateSessionData(int sessionId, byte[] initData, String mimeType) { 116 mSessionId = sessionId; 117 mInitData = initData; 118 mMimeType = mimeType; 119 } 120 121 private int sessionId() { return mSessionId; } 122 private byte[] initData() { return mInitData; } 123 private String mimeType() { return mMimeType; } 124 } 125 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 */ 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 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 */ 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 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.NotProvisionedException e) { 241 // Throw NotProvisionedException so that we can startProvisioning(). 242 throw e; 243 } catch (android.media.MediaDrmException e) { 244 // Other MediaDrmExceptions (e.g. ResourceBusyException) are not 245 // recoverable. 246 Log.e(TAG, "Cannot open a new session", e); 247 release(); 248 return null; 249 } 250 } 251 252 /** 253 * Close a session. 254 * 255 * @param session to be closed. 256 */ 257 private void closeSession(ByteBuffer session) { 258 assert mMediaDrm != null; 259 mMediaDrm.closeSession(session.array()); 260 } 261 262 /** 263 * Check whether the crypto scheme is supported for the given container. 264 * If |containerMimeType| is an empty string, we just return whether 265 * the crypto scheme is supported. 266 * 267 * @return true if the container and the crypto scheme is supported, or 268 * false otherwise. 269 */ 270 @CalledByNative 271 private static boolean isCryptoSchemeSupported(byte[] schemeUUID, String containerMimeType) { 272 UUID cryptoScheme = getUUIDFromBytes(schemeUUID); 273 274 if (containerMimeType.isEmpty()) { 275 return MediaDrm.isCryptoSchemeSupported(cryptoScheme); 276 } 277 278 return MediaDrm.isCryptoSchemeSupported(cryptoScheme, containerMimeType); 279 } 280 281 /** 282 * Create a new MediaDrmBridge from the crypto scheme UUID. 283 * 284 * @param schemeUUID Crypto scheme UUID. 285 * @param securityLevel Security level to be used. 286 * @param nativeMediaDrmBridge Native object of this class. 287 */ 288 @CalledByNative 289 private static MediaDrmBridge create(byte[] schemeUUID, long nativeMediaDrmBridge) { 290 UUID cryptoScheme = getUUIDFromBytes(schemeUUID); 291 if (cryptoScheme == null || !MediaDrm.isCryptoSchemeSupported(cryptoScheme)) { 292 return null; 293 } 294 295 boolean singleSessionMode = false; 296 if (Build.VERSION.RELEASE.equals("4.4")) { 297 singleSessionMode = true; 298 } 299 Log.d(TAG, "MediaDrmBridge uses " + 300 (singleSessionMode ? "single" : "multiple") + "-session mode."); 301 302 MediaDrmBridge mediaDrmBridge = null; 303 try { 304 mediaDrmBridge = new MediaDrmBridge( 305 cryptoScheme, nativeMediaDrmBridge, singleSessionMode); 306 Log.d(TAG, "MediaDrmBridge successfully created."); 307 } catch (android.media.UnsupportedSchemeException e) { 308 Log.e(TAG, "Unsupported DRM scheme", e); 309 } catch (java.lang.IllegalArgumentException e) { 310 Log.e(TAG, "Failed to create MediaDrmBridge", e); 311 } catch (java.lang.IllegalStateException e) { 312 Log.e(TAG, "Failed to create MediaDrmBridge", e); 313 } 314 315 return mediaDrmBridge; 316 } 317 318 /** 319 * Set the security level that the MediaDrm object uses. 320 * This function should be called right after we construct MediaDrmBridge 321 * and before we make any other calls. 322 */ 323 @CalledByNative 324 private boolean setSecurityLevel(String securityLevel) { 325 if (mMediaDrm == null || mMediaCrypto != null) { 326 return false; 327 } 328 329 String currentSecurityLevel = mMediaDrm.getPropertyString(SECURITY_LEVEL); 330 Log.e(TAG, "Security level: current " + currentSecurityLevel + ", new " + securityLevel); 331 if (securityLevel.equals(currentSecurityLevel)) { 332 // No need to set the same security level again. This is not just 333 // a shortcut! Setting the same security level actually causes an 334 // exception in MediaDrm! 335 return true; 336 } 337 338 try { 339 mMediaDrm.setPropertyString(SECURITY_LEVEL, securityLevel); 340 return true; 341 } catch (java.lang.IllegalArgumentException e) { 342 Log.e(TAG, "Failed to set security level " + securityLevel, e); 343 } catch (java.lang.IllegalStateException e) { 344 Log.e(TAG, "Failed to set security level " + securityLevel, e); 345 } 346 347 Log.e(TAG, "Security level " + securityLevel + " not supported!"); 348 return false; 349 } 350 351 /** 352 * Return the MediaCrypto object if available. 353 */ 354 @CalledByNative 355 private MediaCrypto getMediaCrypto() { 356 return mMediaCrypto; 357 } 358 359 /** 360 * Reset the device DRM credentials. 361 */ 362 @CalledByNative 363 private void resetDeviceCredentials() { 364 mResetDeviceCredentialsPending = true; 365 MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest(); 366 PostRequestTask postTask = new PostRequestTask(request.getData()); 367 postTask.execute(request.getDefaultUrl()); 368 } 369 370 /** 371 * Release the MediaDrmBridge object. 372 */ 373 @CalledByNative 374 private void release() { 375 // Do not reset mHandler and mNativeMediaDrmBridge so that we can still 376 // post KeyError back to native code. 377 378 mPendingCreateSessionDataQueue.clear(); 379 mPendingCreateSessionDataQueue = null; 380 381 for (ByteBuffer session : mSessionIds.keySet()) { 382 closeSession(session); 383 } 384 mSessionIds.clear(); 385 mSessionIds = null; 386 mSessionMimeTypes.clear(); 387 mSessionMimeTypes = null; 388 389 // This session was closed in the "for" loop above. 390 mMediaCryptoSession = null; 391 392 if (mMediaCrypto != null) { 393 mMediaCrypto.release(); 394 mMediaCrypto = null; 395 } 396 397 if (mMediaDrm != null) { 398 mMediaDrm.release(); 399 mMediaDrm = null; 400 } 401 } 402 403 /** 404 * Get a key request. 405 * 406 * @param session Session on which we need to get the key request. 407 * @param data Data needed to get the key request. 408 * @param mime Mime type to get the key request. 409 * 410 * @return the key request. 411 */ 412 private MediaDrm.KeyRequest getKeyRequest(ByteBuffer session, byte[] data, String mime) 413 throws android.media.NotProvisionedException { 414 assert mMediaDrm != null; 415 assert mMediaCrypto != null; 416 assert !mProvisioningPending; 417 418 HashMap<String, String> optionalParameters = new HashMap<String, String>(); 419 MediaDrm.KeyRequest request = mMediaDrm.getKeyRequest( 420 session.array(), data, mime, MediaDrm.KEY_TYPE_STREAMING, optionalParameters); 421 String result = (request != null) ? "successed" : "failed"; 422 Log.d(TAG, "getKeyRequest " + result + "!"); 423 return request; 424 } 425 426 /** 427 * Save data to |mPendingCreateSessionDataQueue| so that we can resume the 428 * createSession() call later. 429 */ 430 private void savePendingCreateSessionData(int sessionId, byte[] initData, String mime) { 431 Log.d(TAG, "savePendingCreateSessionData()"); 432 mPendingCreateSessionDataQueue.offer( 433 new PendingCreateSessionData(sessionId, initData, mime)); 434 } 435 436 /** 437 * Process all pending createSession() calls synchronously. 438 */ 439 private void processPendingCreateSessionData() { 440 Log.d(TAG, "processPendingCreateSessionData()"); 441 assert mMediaDrm != null; 442 443 // Check mMediaDrm != null because error may happen in createSession(). 444 // Check !mProvisioningPending because NotProvisionedException may be 445 // thrown in createSession(). 446 while (mMediaDrm != null && !mProvisioningPending && 447 !mPendingCreateSessionDataQueue.isEmpty()) { 448 PendingCreateSessionData pendingData = mPendingCreateSessionDataQueue.poll(); 449 int sessionId = pendingData.sessionId(); 450 byte[] initData = pendingData.initData(); 451 String mime = pendingData.mimeType(); 452 createSession(sessionId, initData, mime); 453 } 454 } 455 456 /** 457 * Process pending operations asynchrnously. 458 */ 459 private void resumePendingOperations() { 460 mHandler.post(new Runnable(){ 461 @Override 462 public void run() { 463 processPendingCreateSessionData(); 464 } 465 }); 466 } 467 468 /** 469 * Create a session with |sessionId|, |initData| and |mime|. 470 * In multiple session mode, a new session will be open. In single session 471 * mode, the mMediaCryptoSession will be used. 472 * 473 * @param sessionId ID for the session to be created. 474 * @param initData Data needed to generate the key request. 475 * @param mime Mime type. 476 */ 477 @CalledByNative 478 private void createSession(int sessionId, byte[] initData, String mime) { 479 Log.d(TAG, "createSession()"); 480 if (mMediaDrm == null) { 481 Log.e(TAG, "createSession() called when MediaDrm is null."); 482 return; 483 } 484 485 if (mProvisioningPending) { 486 assert mMediaCrypto == null; 487 savePendingCreateSessionData(sessionId, initData, mime); 488 return; 489 } 490 491 boolean newSessionOpened = false; 492 ByteBuffer session = null; 493 try { 494 // Create MediaCrypto if necessary. 495 if (mMediaCrypto == null && !createMediaCrypto()) { 496 onSessionError(sessionId); 497 return; 498 } 499 assert mMediaCrypto != null; 500 assert mSessionIds.containsKey(mMediaCryptoSession); 501 502 if (mSingleSessionMode) { 503 session = mMediaCryptoSession; 504 if (mSessionMimeTypes.get(session) != null && 505 !mSessionMimeTypes.get(session).equals(mime)) { 506 Log.e(TAG, "Only one mime type is supported in single session mode."); 507 onSessionError(sessionId); 508 return; 509 } 510 } else { 511 session = openSession(); 512 if (session == null) { 513 Log.e(TAG, "Cannot open session in createSession()."); 514 onSessionError(sessionId); 515 return; 516 } 517 newSessionOpened = true; 518 assert !mSessionIds.containsKey(session); 519 } 520 521 MediaDrm.KeyRequest request = null; 522 request = getKeyRequest(session, initData, mime); 523 if (request == null) { 524 if (newSessionOpened) { 525 closeSession(session); 526 } 527 onSessionError(sessionId); 528 return; 529 } 530 531 onSessionCreated(sessionId, getWebSessionId(session)); 532 onSessionMessage(sessionId, request); 533 if (newSessionOpened) { 534 Log.d(TAG, "createSession(): Session " + getWebSessionId(session) + 535 " (" + sessionId + ") created."); 536 } 537 538 mSessionIds.put(session, sessionId); 539 mSessionMimeTypes.put(session, mime); 540 } catch (android.media.NotProvisionedException e) { 541 Log.e(TAG, "Device not provisioned", e); 542 if (newSessionOpened) { 543 closeSession(session); 544 } 545 savePendingCreateSessionData(sessionId, initData, mime); 546 startProvisioning(); 547 } 548 } 549 550 /** 551 * Returns whether |sessionId| is a valid key session, excluding the media 552 * crypto session in multi-session mode. 553 * 554 * @param sessionId Crypto session Id. 555 */ 556 private boolean sessionExists(ByteBuffer session) { 557 if (mMediaCryptoSession == null) { 558 assert mSessionIds.isEmpty(); 559 Log.e(TAG, "Session doesn't exist because media crypto session is not created."); 560 return false; 561 } 562 assert mSessionIds.containsKey(mMediaCryptoSession); 563 564 if (mSingleSessionMode) { 565 return mMediaCryptoSession.equals(session); 566 } 567 568 return !session.equals(mMediaCryptoSession) && mSessionIds.containsKey(session); 569 } 570 571 /** 572 * Cancel a key request for a session Id. 573 * 574 * @param sessionId Reference ID of session to be released. 575 */ 576 @CalledByNative 577 private void releaseSession(int sessionId) { 578 Log.d(TAG, "releaseSession(): " + sessionId); 579 if (mMediaDrm == null) { 580 Log.e(TAG, "releaseSession() called when MediaDrm is null."); 581 return; 582 } 583 584 ByteBuffer session = getSession(sessionId); 585 if (session == null) { 586 Log.e(TAG, "Invalid sessionId in releaseSession."); 587 onSessionError(sessionId); 588 return; 589 } 590 591 mMediaDrm.removeKeys(session.array()); 592 593 // We don't close the media crypto session in single session mode. 594 if (!mSingleSessionMode) { 595 Log.d(TAG, "Session " + sessionId + "closed."); 596 closeSession(session); 597 mSessionIds.remove(session); 598 onSessionClosed(sessionId); 599 } 600 } 601 602 /** 603 * Add a key for a session Id. 604 * 605 * @param sessionId Reference ID of session to be updated. 606 * @param key Response data from the server. 607 */ 608 @CalledByNative 609 private void updateSession(int sessionId, byte[] key) { 610 Log.d(TAG, "updateSession(): " + sessionId); 611 if (mMediaDrm == null) { 612 Log.e(TAG, "updateSession() called when MediaDrm is null."); 613 return; 614 } 615 616 // TODO(xhwang): We should be able to DCHECK this when WD EME is implemented. 617 ByteBuffer session = getSession(sessionId); 618 if (!sessionExists(session)) { 619 Log.e(TAG, "Invalid session in updateSession."); 620 onSessionError(sessionId); 621 return; 622 } 623 624 try { 625 try { 626 mMediaDrm.provideKeyResponse(session.array(), key); 627 } catch (java.lang.IllegalStateException e) { 628 // This is not really an exception. Some error code are incorrectly 629 // reported as an exception. 630 // TODO(qinmin): remove this exception catch when b/10495563 is fixed. 631 Log.e(TAG, "Exception intentionally caught when calling provideKeyResponse()", e); 632 } 633 onSessionReady(sessionId); 634 Log.d(TAG, "Key successfully added for session " + sessionId); 635 return; 636 } catch (android.media.NotProvisionedException e) { 637 // TODO(xhwang): Should we handle this? 638 Log.e(TAG, "failed to provide key response", e); 639 } catch (android.media.DeniedByServerException e) { 640 Log.e(TAG, "failed to provide key response", e); 641 } 642 onSessionError(sessionId); 643 release(); 644 } 645 646 /** 647 * Return the security level of this DRM object. 648 */ 649 @CalledByNative 650 private String getSecurityLevel() { 651 if (mMediaDrm == null) { 652 Log.e(TAG, "getSecurityLevel() called when MediaDrm is null."); 653 return null; 654 } 655 return mMediaDrm.getPropertyString("securityLevel"); 656 } 657 658 private void startProvisioning() { 659 Log.d(TAG, "startProvisioning"); 660 assert mMediaDrm != null; 661 assert !mProvisioningPending; 662 mProvisioningPending = true; 663 MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest(); 664 PostRequestTask postTask = new PostRequestTask(request.getData()); 665 postTask.execute(request.getDefaultUrl()); 666 } 667 668 /** 669 * Called when the provision response is received. 670 * 671 * @param response Response data from the provision server. 672 */ 673 private void onProvisionResponse(byte[] response) { 674 Log.d(TAG, "onProvisionResponse()"); 675 assert mProvisioningPending; 676 mProvisioningPending = false; 677 678 // If |mMediaDrm| is released, there is no need to callback native. 679 if (mMediaDrm == null) { 680 return; 681 } 682 683 boolean success = provideProvisionResponse(response); 684 685 if (mResetDeviceCredentialsPending) { 686 nativeOnResetDeviceCredentialsCompleted(mNativeMediaDrmBridge, success); 687 mResetDeviceCredentialsPending = false; 688 } 689 690 if (success) { 691 resumePendingOperations(); 692 } 693 } 694 695 /** 696 * Provide the provisioning response to MediaDrm. 697 * @returns false if the response is invalid or on error, true otherwise. 698 */ 699 boolean provideProvisionResponse(byte[] response) { 700 if (response == null || response.length == 0) { 701 Log.e(TAG, "Invalid provision response."); 702 return false; 703 } 704 705 try { 706 mMediaDrm.provideProvisionResponse(response); 707 return true; 708 } catch (android.media.DeniedByServerException e) { 709 Log.e(TAG, "failed to provide provision response", e); 710 } catch (java.lang.IllegalStateException e) { 711 Log.e(TAG, "failed to provide provision response", e); 712 } 713 return false; 714 } 715 716 private void onSessionCreated(final int sessionId, final String webSessionId) { 717 mHandler.post(new Runnable(){ 718 @Override 719 public void run() { 720 nativeOnSessionCreated(mNativeMediaDrmBridge, sessionId, webSessionId); 721 } 722 }); 723 } 724 725 private void onSessionMessage(final int sessionId, final MediaDrm.KeyRequest request) { 726 mHandler.post(new Runnable(){ 727 @Override 728 public void run() { 729 nativeOnSessionMessage(mNativeMediaDrmBridge, sessionId, 730 request.getData(), request.getDefaultUrl()); 731 } 732 }); 733 } 734 735 private void onSessionReady(final int sessionId) { 736 mHandler.post(new Runnable() { 737 @Override 738 public void run() { 739 nativeOnSessionReady(mNativeMediaDrmBridge, sessionId); 740 } 741 }); 742 } 743 744 private void onSessionClosed(final int sessionId) { 745 mHandler.post(new Runnable() { 746 @Override 747 public void run() { 748 nativeOnSessionClosed(mNativeMediaDrmBridge, sessionId); 749 } 750 }); 751 } 752 753 private void onSessionError(final int sessionId) { 754 // TODO(qinmin): pass the error code to native. 755 mHandler.post(new Runnable() { 756 @Override 757 public void run() { 758 nativeOnSessionError(mNativeMediaDrmBridge, sessionId); 759 } 760 }); 761 } 762 763 private String getWebSessionId(ByteBuffer session) { 764 String webSessionId = null; 765 try { 766 webSessionId = new String(session.array(), "UTF-8"); 767 } catch (java.io.UnsupportedEncodingException e) { 768 Log.e(TAG, "getWebSessionId failed", e); 769 } catch (java.lang.NullPointerException e) { 770 Log.e(TAG, "getWebSessionId failed", e); 771 } 772 return webSessionId; 773 } 774 775 private class MediaDrmListener implements MediaDrm.OnEventListener { 776 @Override 777 public void onEvent( 778 MediaDrm mediaDrm, byte[] sessionArray, int event, int extra, byte[] data) { 779 if (sessionArray == null) { 780 Log.e(TAG, "MediaDrmListener: Null session."); 781 return; 782 } 783 ByteBuffer session = ByteBuffer.wrap(sessionArray); 784 if (!sessionExists(session)) { 785 Log.e(TAG, "MediaDrmListener: Invalid session."); 786 return; 787 } 788 Integer sessionId = mSessionIds.get(session); 789 if (sessionId == null || sessionId == INVALID_SESSION_ID) { 790 Log.e(TAG, "MediaDrmListener: Invalid session ID."); 791 return; 792 } 793 switch(event) { 794 case MediaDrm.EVENT_PROVISION_REQUIRED: 795 Log.d(TAG, "MediaDrm.EVENT_PROVISION_REQUIRED"); 796 break; 797 case MediaDrm.EVENT_KEY_REQUIRED: 798 Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED"); 799 if (mProvisioningPending) { 800 return; 801 } 802 String mime = mSessionMimeTypes.get(session); 803 MediaDrm.KeyRequest request = null; 804 try { 805 request = getKeyRequest(session, data, mime); 806 } catch (android.media.NotProvisionedException e) { 807 Log.e(TAG, "Device not provisioned", e); 808 startProvisioning(); 809 return; 810 } 811 if (request != null) { 812 onSessionMessage(sessionId, request); 813 } else { 814 onSessionError(sessionId); 815 } 816 break; 817 case MediaDrm.EVENT_KEY_EXPIRED: 818 Log.d(TAG, "MediaDrm.EVENT_KEY_EXPIRED"); 819 onSessionError(sessionId); 820 break; 821 case MediaDrm.EVENT_VENDOR_DEFINED: 822 Log.d(TAG, "MediaDrm.EVENT_VENDOR_DEFINED"); 823 assert false; // Should never happen. 824 break; 825 default: 826 Log.e(TAG, "Invalid DRM event " + event); 827 return; 828 } 829 } 830 } 831 832 private class PostRequestTask extends AsyncTask<String, Void, Void> { 833 private static final String TAG = "PostRequestTask"; 834 835 private byte[] mDrmRequest; 836 private byte[] mResponseBody; 837 838 public PostRequestTask(byte[] drmRequest) { 839 mDrmRequest = drmRequest; 840 } 841 842 @Override 843 protected Void doInBackground(String... urls) { 844 mResponseBody = postRequest(urls[0], mDrmRequest); 845 if (mResponseBody != null) { 846 Log.d(TAG, "response length=" + mResponseBody.length); 847 } 848 return null; 849 } 850 851 private byte[] postRequest(String url, byte[] drmRequest) { 852 HttpClient httpClient = new DefaultHttpClient(); 853 HttpPost httpPost = new HttpPost(url + "&signedRequest=" + new String(drmRequest)); 854 855 Log.d(TAG, "PostRequest:" + httpPost.getRequestLine()); 856 try { 857 // Add data 858 httpPost.setHeader("Accept", "*/*"); 859 httpPost.setHeader("User-Agent", "Widevine CDM v1.0"); 860 httpPost.setHeader("Content-Type", "application/json"); 861 862 // Execute HTTP Post Request 863 HttpResponse response = httpClient.execute(httpPost); 864 865 byte[] responseBody; 866 int responseCode = response.getStatusLine().getStatusCode(); 867 if (responseCode == 200) { 868 responseBody = EntityUtils.toByteArray(response.getEntity()); 869 } else { 870 Log.d(TAG, "Server returned HTTP error code " + responseCode); 871 return null; 872 } 873 return responseBody; 874 } catch (ClientProtocolException e) { 875 e.printStackTrace(); 876 } catch (IOException e) { 877 e.printStackTrace(); 878 } 879 return null; 880 } 881 882 @Override 883 protected void onPostExecute(Void v) { 884 onProvisionResponse(mResponseBody); 885 } 886 } 887 888 public static void addKeySystemUuidMapping(String keySystem, UUID uuid) { 889 ByteBuffer uuidBuffer = ByteBuffer.allocateDirect(16); 890 // MSB (byte) should be positioned at the first element. 891 uuidBuffer.order(ByteOrder.BIG_ENDIAN); 892 uuidBuffer.putLong(uuid.getMostSignificantBits()); 893 uuidBuffer.putLong(uuid.getLeastSignificantBits()); 894 nativeAddKeySystemUuidMapping(keySystem, uuidBuffer); 895 } 896 897 private native void nativeOnMediaCryptoReady(long nativeMediaDrmBridge); 898 899 private native void nativeOnSessionCreated(long nativeMediaDrmBridge, int sessionId, 900 String webSessionId); 901 902 private native void nativeOnSessionMessage(long nativeMediaDrmBridge, int sessionId, 903 byte[] message, String destinationUrl); 904 905 private native void nativeOnSessionReady(long nativeMediaDrmBridge, int sessionId); 906 907 private native void nativeOnSessionClosed(long nativeMediaDrmBridge, int sessionId); 908 909 private native void nativeOnSessionError(long nativeMediaDrmBridge, int sessionId); 910 911 private native void nativeOnResetDeviceCredentialsCompleted( 912 long nativeMediaDrmBridge, boolean success); 913 914 private static native void nativeAddKeySystemUuidMapping(String keySystem, ByteBuffer uuid); 915} 916