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.
5 package org.chromium.media;
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;
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;
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;
31 * A wrapper of the android MediaDrm class. Each MediaDrmBridge manages multiple
32 * sessions for a single MediaSourcePlayer.
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
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).
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;
70 private MediaDrm mMediaDrm;
71 private long mNativeMediaDrmBridge;
72 private UUID mSchemeUUID;
73 private Handler mHandler;
75 // In this mode, we only open one session, i.e. mMediaCryptoSession.
76 private boolean mSingleSessionMode;
78 // A session only for the purpose of creating a MediaCrypto object.
79 // This session is opened when createSession() is called for the first
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;
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;
95 // The queue of all pending createSession() data.
96 private ArrayDeque<PendingCreateSessionData> mPendingCreateSessionDataQueue;
98 private boolean mResetDeviceCredentialsPending;
100 // MediaDrmBridge is waiting for provisioning response from the server.
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;
108 * This class contains data needed to call createSession().
110 private static class PendingCreateSessionData {
111 private final int mSessionId;
112 private final byte[] mInitData;
113 private final String mMimeType;
115 private PendingCreateSessionData(int sessionId, byte[] initData, String mimeType) {
116 mSessionId = sessionId;
117 mInitData = initData;
118 mMimeType = mimeType;
121 private int sessionId() { return mSessionId; }
122 private byte[] initData() { return mInitData; }
123 private String mimeType() { return mMimeType; }
126 private static UUID getUUIDFromBytes(byte[] data) {
127 if (data.length != 16) {
130 long mostSigBits = 0;
131 long leastSigBits = 0;
132 for (int i = 0; i < 8; i++) {
133 mostSigBits = (mostSigBits << 8) | (data[i] & 0xff);
135 for (int i = 8; i < 16; i++) {
136 leastSigBits = (leastSigBits << 8) | (data[i] & 0xff);
138 return new UUID(mostSigBits, leastSigBits);
142 * Gets session associated with the sessionId.
144 * @return session if sessionId maps a valid opened session. Returns null
147 private ByteBuffer getSession(int sessionId) {
148 for (ByteBuffer session : mSessionIds.keySet()) {
149 if (mSessionIds.get(session) == sessionId) {
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;
169 mMediaDrm.setOnEventListener(new MediaDrmListener());
170 mMediaDrm.setPropertyString(PRIVACY_MODE, ENABLE);
171 if (!mSingleSessionMode) {
172 mMediaDrm.setPropertyString(SESSION_SHARING, ENABLE);
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.
183 * Create a MediaCrypto object.
185 * @return whether a MediaCrypto object is successfully created.
187 private boolean createMediaCrypto() throws android.media.NotProvisionedException {
188 if (mMediaDrm == null) {
191 assert !mProvisioningPending;
192 assert mMediaCryptoSession == null;
193 assert mMediaCrypto == null;
195 // Open media crypto session.
196 mMediaCryptoSession = openSession();
197 if (mMediaCryptoSession == null) {
198 Log.e(TAG, "Cannot create MediaCrypto Session.");
201 Log.d(TAG, "MediaCrypto Session created: " + mMediaCryptoSession);
203 // Create MediaCrypto object.
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);
214 Log.e(TAG, "Cannot create MediaCrypto for unsupported scheme.");
216 } catch (android.media.MediaCryptoException e) {
217 Log.e(TAG, "Cannot create MediaCrypto", e);
225 * Open a new session..
227 * @return the session opened. Returns null if unexpected error happened.
229 private ByteBuffer openSession() throws android.media.NotProvisionedException {
230 assert mMediaDrm != null;
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);
240 } catch (android.media.NotProvisionedException e) {
241 // Throw NotProvisionedException so that we can startProvisioning().
243 } catch (android.media.MediaDrmException e) {
244 // Other MediaDrmExceptions (e.g. ResourceBusyException) are not
246 Log.e(TAG, "Cannot open a new session", e);
255 * @param session to be closed.
257 private void closeSession(ByteBuffer session) {
258 assert mMediaDrm != null;
259 mMediaDrm.closeSession(session.array());
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.
267 * @return true if the container and the crypto scheme is supported, or
271 private static boolean isCryptoSchemeSupported(byte[] schemeUUID, String containerMimeType) {
272 UUID cryptoScheme = getUUIDFromBytes(schemeUUID);
274 if (containerMimeType.isEmpty()) {
275 return MediaDrm.isCryptoSchemeSupported(cryptoScheme);
278 return MediaDrm.isCryptoSchemeSupported(cryptoScheme, containerMimeType);
282 * Create a new MediaDrmBridge from the crypto scheme UUID.
284 * @param schemeUUID Crypto scheme UUID.
285 * @param securityLevel Security level to be used.
286 * @param nativeMediaDrmBridge Native object of this class.
289 private static MediaDrmBridge create(byte[] schemeUUID, long nativeMediaDrmBridge) {
290 UUID cryptoScheme = getUUIDFromBytes(schemeUUID);
291 if (cryptoScheme == null || !MediaDrm.isCryptoSchemeSupported(cryptoScheme)) {
295 boolean singleSessionMode = false;
296 if (Build.VERSION.RELEASE.equals("4.4")) {
297 singleSessionMode = true;
299 Log.d(TAG, "MediaDrmBridge uses " +
300 (singleSessionMode ? "single" : "multiple") + "-session mode.");
302 MediaDrmBridge mediaDrmBridge = null;
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);
315 return mediaDrmBridge;
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.
324 private boolean setSecurityLevel(String securityLevel) {
325 if (mMediaDrm == null || mMediaCrypto != null) {
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!
339 mMediaDrm.setPropertyString(SECURITY_LEVEL, securityLevel);
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);
347 Log.e(TAG, "Security level " + securityLevel + " not supported!");
352 * Return the MediaCrypto object if available.
355 private MediaCrypto getMediaCrypto() {
360 * Reset the device DRM credentials.
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());
371 * Release the MediaDrmBridge object.
374 private void release() {
375 // Do not reset mHandler and mNativeMediaDrmBridge so that we can still
376 // post KeyError back to native code.
378 mPendingCreateSessionDataQueue.clear();
379 mPendingCreateSessionDataQueue = null;
381 for (ByteBuffer session : mSessionIds.keySet()) {
382 closeSession(session);
386 mSessionMimeTypes.clear();
387 mSessionMimeTypes = null;
389 // This session was closed in the "for" loop above.
390 mMediaCryptoSession = null;
392 if (mMediaCrypto != null) {
393 mMediaCrypto.release();
397 if (mMediaDrm != null) {
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.
410 * @return the key request.
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;
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 + "!");
427 * Save data to |mPendingCreateSessionDataQueue| so that we can resume the
428 * createSession() call later.
430 private void savePendingCreateSessionData(int sessionId, byte[] initData, String mime) {
431 Log.d(TAG, "savePendingCreateSessionData()");
432 mPendingCreateSessionDataQueue.offer(
433 new PendingCreateSessionData(sessionId, initData, mime));
437 * Process all pending createSession() calls synchronously.
439 private void processPendingCreateSessionData() {
440 Log.d(TAG, "processPendingCreateSessionData()");
441 assert mMediaDrm != null;
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);
457 * Process pending operations asynchrnously.
459 private void resumePendingOperations() {
460 mHandler.post(new Runnable(){
463 processPendingCreateSessionData();
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.
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.
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.");
485 if (mProvisioningPending) {
486 assert mMediaCrypto == null;
487 savePendingCreateSessionData(sessionId, initData, mime);
491 boolean newSessionOpened = false;
492 ByteBuffer session = null;
494 // Create MediaCrypto if necessary.
495 if (mMediaCrypto == null && !createMediaCrypto()) {
496 onSessionError(sessionId);
499 assert mMediaCrypto != null;
500 assert mSessionIds.containsKey(mMediaCryptoSession);
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);
511 session = openSession();
512 if (session == null) {
513 Log.e(TAG, "Cannot open session in createSession().");
514 onSessionError(sessionId);
517 newSessionOpened = true;
518 assert !mSessionIds.containsKey(session);
521 MediaDrm.KeyRequest request = null;
522 request = getKeyRequest(session, initData, mime);
523 if (request == null) {
524 if (newSessionOpened) {
525 closeSession(session);
527 onSessionError(sessionId);
531 onSessionCreated(sessionId, getWebSessionId(session));
532 onSessionMessage(sessionId, request);
533 if (newSessionOpened) {
534 Log.d(TAG, "createSession(): Session " + getWebSessionId(session) +
535 " (" + sessionId + ") created.");
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);
545 savePendingCreateSessionData(sessionId, initData, mime);
551 * Returns whether |sessionId| is a valid key session, excluding the media
552 * crypto session in multi-session mode.
554 * @param sessionId Crypto session Id.
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.");
562 assert mSessionIds.containsKey(mMediaCryptoSession);
564 if (mSingleSessionMode) {
565 return mMediaCryptoSession.equals(session);
568 return !session.equals(mMediaCryptoSession) && mSessionIds.containsKey(session);
572 * Cancel a key request for a session Id.
574 * @param sessionId Reference ID of session to be released.
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.");
584 ByteBuffer session = getSession(sessionId);
585 if (session == null) {
586 Log.e(TAG, "Invalid sessionId in releaseSession.");
587 onSessionError(sessionId);
591 mMediaDrm.removeKeys(session.array());
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);
603 * Add a key for a session Id.
605 * @param sessionId Reference ID of session to be updated.
606 * @param key Response data from the server.
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.");
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);
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);
633 onSessionReady(sessionId);
634 Log.d(TAG, "Key successfully added for session " + sessionId);
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);
642 onSessionError(sessionId);
647 * Return the security level of this DRM object.
650 private String getSecurityLevel() {
651 if (mMediaDrm == null) {
652 Log.e(TAG, "getSecurityLevel() called when MediaDrm is null.");
655 return mMediaDrm.getPropertyString("securityLevel");
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());
669 * Called when the provision response is received.
671 * @param response Response data from the provision server.
673 private void onProvisionResponse(byte[] response) {
674 Log.d(TAG, "onProvisionResponse()");
675 assert mProvisioningPending;
676 mProvisioningPending = false;
678 // If |mMediaDrm| is released, there is no need to callback native.
679 if (mMediaDrm == null) {
683 boolean success = provideProvisionResponse(response);
685 if (mResetDeviceCredentialsPending) {
686 nativeOnResetDeviceCredentialsCompleted(mNativeMediaDrmBridge, success);
687 mResetDeviceCredentialsPending = false;
691 resumePendingOperations();
696 * Provide the provisioning response to MediaDrm.
697 * @returns false if the response is invalid or on error, true otherwise.
699 boolean provideProvisionResponse(byte[] response) {
700 if (response == null || response.length == 0) {
701 Log.e(TAG, "Invalid provision response.");
706 mMediaDrm.provideProvisionResponse(response);
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);
716 private void onSessionCreated(final int sessionId, final String webSessionId) {
717 mHandler.post(new Runnable(){
720 nativeOnSessionCreated(mNativeMediaDrmBridge, sessionId, webSessionId);
725 private void onSessionMessage(final int sessionId, final MediaDrm.KeyRequest request) {
726 mHandler.post(new Runnable(){
729 nativeOnSessionMessage(mNativeMediaDrmBridge, sessionId,
730 request.getData(), request.getDefaultUrl());
735 private void onSessionReady(final int sessionId) {
736 mHandler.post(new Runnable() {
739 nativeOnSessionReady(mNativeMediaDrmBridge, sessionId);
744 private void onSessionClosed(final int sessionId) {
745 mHandler.post(new Runnable() {
748 nativeOnSessionClosed(mNativeMediaDrmBridge, sessionId);
753 private void onSessionError(final int sessionId) {
754 // TODO(qinmin): pass the error code to native.
755 mHandler.post(new Runnable() {
758 nativeOnSessionError(mNativeMediaDrmBridge, sessionId);
763 private String getWebSessionId(ByteBuffer session) {
764 String webSessionId = null;
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);
775 private class MediaDrmListener implements MediaDrm.OnEventListener {
778 MediaDrm mediaDrm, byte[] sessionArray, int event, int extra, byte[] data) {
779 if (sessionArray == null) {
780 Log.e(TAG, "MediaDrmListener: Null session.");
783 ByteBuffer session = ByteBuffer.wrap(sessionArray);
784 if (!sessionExists(session)) {
785 Log.e(TAG, "MediaDrmListener: Invalid session.");
788 Integer sessionId = mSessionIds.get(session);
789 if (sessionId == null || sessionId == INVALID_SESSION_ID) {
790 Log.e(TAG, "MediaDrmListener: Invalid session ID.");
794 case MediaDrm.EVENT_PROVISION_REQUIRED:
795 Log.d(TAG, "MediaDrm.EVENT_PROVISION_REQUIRED");
797 case MediaDrm.EVENT_KEY_REQUIRED:
798 Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED");
799 if (mProvisioningPending) {
802 String mime = mSessionMimeTypes.get(session);
803 MediaDrm.KeyRequest request = null;
805 request = getKeyRequest(session, data, mime);
806 } catch (android.media.NotProvisionedException e) {
807 Log.e(TAG, "Device not provisioned", e);
811 if (request != null) {
812 onSessionMessage(sessionId, request);
814 onSessionError(sessionId);
817 case MediaDrm.EVENT_KEY_EXPIRED:
818 Log.d(TAG, "MediaDrm.EVENT_KEY_EXPIRED");
819 onSessionError(sessionId);
821 case MediaDrm.EVENT_VENDOR_DEFINED:
822 Log.d(TAG, "MediaDrm.EVENT_VENDOR_DEFINED");
823 assert false; // Should never happen.
826 Log.e(TAG, "Invalid DRM event " + event);
832 private class PostRequestTask extends AsyncTask<String, Void, Void> {
833 private static final String TAG = "PostRequestTask";
835 private byte[] mDrmRequest;
836 private byte[] mResponseBody;
838 public PostRequestTask(byte[] drmRequest) {
839 mDrmRequest = drmRequest;
843 protected Void doInBackground(String... urls) {
844 mResponseBody = postRequest(urls[0], mDrmRequest);
845 if (mResponseBody != null) {
846 Log.d(TAG, "response length=" + mResponseBody.length);
851 private byte[] postRequest(String url, byte[] drmRequest) {
852 HttpClient httpClient = new DefaultHttpClient();
853 HttpPost httpPost = new HttpPost(url + "&signedRequest=" + new String(drmRequest));
855 Log.d(TAG, "PostRequest:" + httpPost.getRequestLine());
858 httpPost.setHeader("Accept", "*/*");
859 httpPost.setHeader("User-Agent", "Widevine CDM v1.0");
860 httpPost.setHeader("Content-Type", "application/json");
862 // Execute HTTP Post Request
863 HttpResponse response = httpClient.execute(httpPost);
866 int responseCode = response.getStatusLine().getStatusCode();
867 if (responseCode == 200) {
868 responseBody = EntityUtils.toByteArray(response.getEntity());
870 Log.d(TAG, "Server returned HTTP error code " + responseCode);
874 } catch (ClientProtocolException e) {
876 } catch (IOException e) {
883 protected void onPostExecute(Void v) {
884 onProvisionResponse(mResponseBody);
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);
897 private native void nativeOnMediaCryptoReady(long nativeMediaDrmBridge);
899 private native void nativeOnSessionCreated(long nativeMediaDrmBridge, int sessionId,
900 String webSessionId);
902 private native void nativeOnSessionMessage(long nativeMediaDrmBridge, int sessionId,
903 byte[] message, String destinationUrl);
905 private native void nativeOnSessionReady(long nativeMediaDrmBridge, int sessionId);
907 private native void nativeOnSessionClosed(long nativeMediaDrmBridge, int sessionId);
909 private native void nativeOnSessionError(long nativeMediaDrmBridge, int sessionId);
911 private native void nativeOnResetDeviceCredentialsCompleted(
912 long nativeMediaDrmBridge, boolean success);
914 private static native void nativeAddKeySystemUuidMapping(String keySystem, ByteBuffer uuid);