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