// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.media;
import android.media.MediaCrypto;
import android.media.MediaDrm;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.UUID;
/**
* A wrapper of the android MediaDrm class. Each MediaDrmBridge manages multiple
* sessions for a single MediaSourcePlayer.
*/
@JNINamespace("media")
class MediaDrmBridge {
// Implementation Notes:
// - A media crypto session (mMediaCryptoSession) is opened after MediaDrm
// is created. This session will be added to mSessionIds.
// a) In multiple session mode, this session will only be used to create
// the MediaCrypto object. It's associated mime type is always null and
// it's session ID is always INVALID_SESSION_ID.
// b) In single session mode, this session will be used to create the
// MediaCrypto object and will be used to call getKeyRequest() and
// manage all keys. The session ID will always be the lastest session
// ID passed by the caller.
// - Each createSession() call creates a new session. All sessions are
// managed in mSessionIds.
// - Whenever NotProvisionedException is thrown, we will clean up the
// current state and start the provisioning process.
// - When provisioning is finished, we will try to resume suspended
// operations:
// a) Create the media crypto session if it's not created.
// b) Finish createSession() if previous createSession() was interrupted
// by a NotProvisionedException.
// - Whenever an unexpected error occurred, we'll call release() to release
// all resources and clear all states. In that case all calls to this
// object will be no-op. All public APIs and callbacks should check
// mMediaBridge to make sure release() hasn't been called. Also, we call
// release() immediately after the error happens (e.g. after mMediaDrm)
// calls. Indirect calls should not call release() again to avoid
// duplication (even though it doesn't hurt to call release() twice).
private static final String TAG = "MediaDrmBridge";
private static final String SECURITY_LEVEL = "securityLevel";
private static final String PRIVACY_MODE = "privacyMode";
private static final String SESSION_SHARING = "sessionSharing";
private static final String ENABLE = "enable";
private static final int INVALID_SESSION_ID = 0;
private MediaDrm mMediaDrm;
private long mNativeMediaDrmBridge;
private UUID mSchemeUUID;
private Handler mHandler;
// In this mode, we only open one session, i.e. mMediaCryptoSession.
private boolean mSingleSessionMode;
// A session only for the purpose of creating a MediaCrypto object.
// This session is opened when createSession() is called for the first
// time.
// - In multiple session mode, all following createSession() calls
// should create a new session and use it to call getKeyRequest(). No
// getKeyRequest() should ever be called on this media crypto session.
// - In single session mode, all createSession() calls use the same
// media crypto session. When createSession() is called with a new
// initData, previously added keys may not be available anymore.
private ByteBuffer mMediaCryptoSession;
private MediaCrypto mMediaCrypto;
// The map of all opened sessions to their session reference IDs.
private HashMap<ByteBuffer, Integer> mSessionIds;
// The map of all opened sessions to their mime types.
private HashMap<ByteBuffer, String> mSessionMimeTypes;
// The queue of all pending createSession() data.
private ArrayDeque<PendingCreateSessionData> mPendingCreateSessionDataQueue;
private boolean mResetDeviceCredentialsPending;
// MediaDrmBridge is waiting for provisioning response from the server.
//
// Notes about NotProvisionedException: This exception can be thrown in a
// lot of cases. To streamline implementation, we do not catch it in private
// non-native methods and only catch it in public APIs.
private boolean mProvisioningPending;
/**
* This class contains data needed to call createSession().
*/
private static class PendingCreateSessionData {
private final int mSessionId;
private final byte[] mInitData;
private final String mMimeType;
private PendingCreateSessionData(int sessionId, byte[] initData, String mimeType) {
mSessionId = sessionId;
mInitData = initData;
mMimeType = mimeType;
}
private int sessionId() { return mSessionId; }
private byte[] initData() { return mInitData; }
private String mimeType() { return mMimeType; }
}
private static UUID getUUIDFromBytes(byte[] data) {
if (data.length != 16) {
return null;
}
long mostSigBits = 0;
long leastSigBits = 0;
for (int i = 0; i < 8; i++) {
mostSigBits = (mostSigBits << 8) | (data[i] & 0xff);
}
for (int i = 8; i < 16; i++) {
leastSigBits = (leastSigBits << 8) | (data[i] & 0xff);
}
return new UUID(mostSigBits, leastSigBits);
}
/**
* Gets session associated with the sessionId.
*
* @return session if sessionId maps a valid opened session. Returns null
* otherwise.
*/
private ByteBuffer getSession(int sessionId) {
for (ByteBuffer session : mSessionIds.keySet()) {
if (mSessionIds.get(session) == sessionId) {
return session;
}
}
return null;
}
private MediaDrmBridge(UUID schemeUUID, long nativeMediaDrmBridge, boolean singleSessionMode)
throws android.media.UnsupportedSchemeException {
mSchemeUUID = schemeUUID;
mMediaDrm = new MediaDrm(schemeUUID);
mNativeMediaDrmBridge = nativeMediaDrmBridge;
mHandler = new Handler();
mSingleSessionMode = singleSessionMode;
mSessionIds = new HashMap<ByteBuffer, Integer>();
mSessionMimeTypes = new HashMap<ByteBuffer, String>();
mPendingCreateSessionDataQueue = new ArrayDeque<PendingCreateSessionData>();
mResetDeviceCredentialsPending = false;
mProvisioningPending = false;
mMediaDrm.setOnEventListener(new MediaDrmListener());
mMediaDrm.setPropertyString(PRIVACY_MODE, ENABLE);
if (!mSingleSessionMode) {
mMediaDrm.setPropertyString(SESSION_SHARING, ENABLE);
}
// We could open a MediaCrypto session here to support faster start of
// clear lead (no need to wait for createSession()). But on
// Android, memory and battery resources are precious and we should
// only create a session when we are sure we'll use it.
// TODO(xhwang): Investigate other options to support fast start.
}
/**
* Create a MediaCrypto object.
*
* @return whether a MediaCrypto object is successfully created.
*/
private boolean createMediaCrypto() throws android.media.NotProvisionedException {
if (mMediaDrm == null) {
return false;
}
assert !mProvisioningPending;
assert mMediaCryptoSession == null;
assert mMediaCrypto == null;
// Open media crypto session.
mMediaCryptoSession = openSession();
if (mMediaCryptoSession == null) {
Log.e(TAG, "Cannot create MediaCrypto Session.");
return false;
}
Log.d(TAG, "MediaCrypto Session created: " + mMediaCryptoSession);
// Create MediaCrypto object.
try {
if (MediaCrypto.isCryptoSchemeSupported(mSchemeUUID)) {
final byte[] mediaCryptoSession = mMediaCryptoSession.array();
mMediaCrypto = new MediaCrypto(mSchemeUUID, mediaCryptoSession);
assert mMediaCrypto != null;
Log.d(TAG, "MediaCrypto successfully created!");
mSessionIds.put(mMediaCryptoSession, INVALID_SESSION_ID);
// Notify the native code that MediaCrypto is ready.
nativeOnMediaCryptoReady(mNativeMediaDrmBridge);
return true;
} else {
Log.e(TAG, "Cannot create MediaCrypto for unsupported scheme.");
}
} catch (android.media.MediaCryptoException e) {
Log.e(TAG, "Cannot create MediaCrypto", e);
}
release();
return false;
}
/**
* Open a new session..
*
* @return the session opened. Returns null if unexpected error happened.
*/
private ByteBuffer openSession() throws android.media.NotProvisionedException {
assert mMediaDrm != null;
try {
byte[] session = mMediaDrm.openSession();
// ByteBuffer.wrap() is backed by the byte[]. Make a clone here in
// case the underlying byte[] is modified.
return ByteBuffer.wrap(session.clone());
} catch (java.lang.RuntimeException e) { // TODO(xhwang): Drop this?
Log.e(TAG, "Cannot open a new session", e);
release();
return null;
}
}
/**
* Close a session.
*
* @param session to be closed.
*/
private void closeSession(ByteBuffer session) {
assert mMediaDrm != null;
mMediaDrm.closeSession(session.array());
}
/**
* Check whether the crypto scheme is supported for the given container.
* If |containerMimeType| is an empty string, we just return whether
* the crypto scheme is supported.
* TODO(qinmin): Implement the checking for container.
*
* @return true if the container and the crypto scheme is supported, or
* false otherwise.
*/
@CalledByNative
private static boolean isCryptoSchemeSupported(byte[] schemeUUID, String containerMimeType) {
UUID cryptoScheme = getUUIDFromBytes(schemeUUID);
return MediaDrm.isCryptoSchemeSupported(cryptoScheme);
}
/**
* Create a new MediaDrmBridge from the crypto scheme UUID.
*
* @param schemeUUID Crypto scheme UUID.
* @param securityLevel Security level to be used.
* @param nativeMediaDrmBridge Native object of this class.
*/
@CalledByNative
private static MediaDrmBridge create(byte[] schemeUUID, int nativeMediaDrmBridge) {
UUID cryptoScheme = getUUIDFromBytes(schemeUUID);
if (cryptoScheme == null || !MediaDrm.isCryptoSchemeSupported(cryptoScheme)) {
return null;
}
boolean singleSessionMode = false;
if (Build.VERSION.RELEASE.equals("4.4")) {
singleSessionMode = true;
}
Log.d(TAG, "MediaDrmBridge uses " +
(singleSessionMode ? "single" : "multiple") + "-session mode.");
MediaDrmBridge mediaDrmBridge = null;
try {
mediaDrmBridge = new MediaDrmBridge(
cryptoScheme, nativeMediaDrmBridge, singleSessionMode);
Log.d(TAG, "MediaDrmBridge successfully created.");
} catch (android.media.UnsupportedSchemeException e) {
Log.e(TAG, "Unsupported DRM scheme", e);
} catch (java.lang.IllegalArgumentException e) {
Log.e(TAG, "Failed to create MediaDrmBridge", e);
} catch (java.lang.IllegalStateException e) {
Log.e(TAG, "Failed to create MediaDrmBridge", e);
}
return mediaDrmBridge;
}
/**
* Set the security level that the MediaDrm object uses.
* This function should be called right after we construct MediaDrmBridge
* and before we make any other calls.
*/
@CalledByNative
private boolean setSecurityLevel(String securityLevel) {
if (mMediaDrm == null || mMediaCrypto != null) {
return false;
}
String currentSecurityLevel = mMediaDrm.getPropertyString(SECURITY_LEVEL);
Log.e(TAG, "Security level: current " + currentSecurityLevel + ", new " + securityLevel);
if (securityLevel.equals(currentSecurityLevel)) {
// No need to set the same security level again. This is not just
// a shortcut! Setting the same security level actually causes an
// exception in MediaDrm!
return true;
}
try {
mMediaDrm.setPropertyString(SECURITY_LEVEL, securityLevel);
return true;
} catch (java.lang.IllegalArgumentException e) {
Log.e(TAG, "Failed to set security level " + securityLevel, e);
} catch (java.lang.IllegalStateException e) {
Log.e(TAG, "Failed to set security level " + securityLevel, e);
}
Log.e(TAG, "Security level " + securityLevel + " not supported!");
return false;
}
/**
* Return the MediaCrypto object if available.
*/
@CalledByNative
private MediaCrypto getMediaCrypto() {
return mMediaCrypto;
}
/**
* Reset the device DRM credentials.
*/
@CalledByNative
private void resetDeviceCredentials() {
mResetDeviceCredentialsPending = true;
MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest();
PostRequestTask postTask = new PostRequestTask(request.getData());
postTask.execute(request.getDefaultUrl());
}
/**
* Release the MediaDrmBridge object.
*/
@CalledByNative
private void release() {
// Do not reset mHandler and mNativeMediaDrmBridge so that we can still
// post KeyError back to native code.
mPendingCreateSessionDataQueue.clear();
mPendingCreateSessionDataQueue = null;
for (ByteBuffer session : mSessionIds.keySet()) {
closeSession(session);
}
mSessionIds.clear();
mSessionIds = null;
mSessionMimeTypes.clear();
mSessionMimeTypes = null;
// This session was closed in the "for" loop above.
mMediaCryptoSession = null;
if (mMediaCrypto != null) {
mMediaCrypto.release();
mMediaCrypto = null;
}
if (mMediaDrm != null) {
mMediaDrm.release();
mMediaDrm = null;
}
}
/**
* Get a key request.
*
* @param session Session on which we need to get the key request.
* @param data Data needed to get the key request.
* @param mime Mime type to get the key request.
*
* @return the key request.
*/
private MediaDrm.KeyRequest getKeyRequest(ByteBuffer session, byte[] data, String mime)
throws android.media.NotProvisionedException {
assert mMediaDrm != null;
assert mMediaCrypto != null;
assert !mProvisioningPending;
HashMap<String, String> optionalParameters = new HashMap<String, String>();
MediaDrm.KeyRequest request = mMediaDrm.getKeyRequest(
session.array(), data, mime, MediaDrm.KEY_TYPE_STREAMING, optionalParameters);
String result = (request != null) ? "successed" : "failed";
Log.d(TAG, "getKeyRequest " + result + "!");
return request;
}
/**
* Save data to |mPendingCreateSessionDataQueue| so that we can resume the
* createSession() call later.
*/
private void savePendingCreateSessionData(int sessionId, byte[] initData, String mime) {
Log.d(TAG, "savePendingCreateSessionData()");
mPendingCreateSessionDataQueue.offer(
new PendingCreateSessionData(sessionId, initData, mime));
}
/**
* Process all pending createSession() calls synchronously.
*/
private void processPendingCreateSessionData() {
Log.d(TAG, "processPendingCreateSessionData()");
assert mMediaDrm != null;
// Check mMediaDrm != null because error may happen in createSession().
// Check !mProvisioningPending because NotProvisionedException may be
// thrown in createSession().
while (mMediaDrm != null && !mProvisioningPending &&
!mPendingCreateSessionDataQueue.isEmpty()) {
PendingCreateSessionData pendingData = mPendingCreateSessionDataQueue.poll();
int sessionId = pendingData.sessionId();
byte[] initData = pendingData.initData();
String mime = pendingData.mimeType();
createSession(sessionId, initData, mime);
}
}
/**
* Process pending operations asynchrnously.
*/
private void resumePendingOperations() {
mHandler.post(new Runnable(){
@Override
public void run() {
processPendingCreateSessionData();
}
});
}
/**
* Create a session with |sessionId|, |initData| and |mime|.
* In multiple session mode, a new session will be open. In single session
* mode, the mMediaCryptoSession will be used.
*
* @param sessionId ID for the session to be created.
* @param initData Data needed to generate the key request.
* @param mime Mime type.
*/
@CalledByNative
private void createSession(int sessionId, byte[] initData, String mime) {
Log.d(TAG, "createSession()");
if (mMediaDrm == null) {
Log.e(TAG, "createSession() called when MediaDrm is null.");
return;
}
if (mProvisioningPending) {
assert mMediaCrypto == null;
savePendingCreateSessionData(sessionId, initData, mime);
return;
}
boolean newSessionOpened = false;
ByteBuffer session = null;
try {
// Create MediaCrypto if necessary.
if (mMediaCrypto == null && !createMediaCrypto()) {
onSessionError(sessionId);
return;
}
assert mMediaCrypto != null;
assert mSessionIds.containsKey(mMediaCryptoSession);
if (mSingleSessionMode) {
session = mMediaCryptoSession;
if (mSessionMimeTypes.get(session) != null &&
!mSessionMimeTypes.get(session).equals(mime)) {
Log.e(TAG, "Only one mime type is supported in single session mode.");
onSessionError(sessionId);
return;
}
} else {
session = openSession();
if (session == null) {
Log.e(TAG, "Cannot open session in createSession().");
onSessionError(sessionId);
return;
}
newSessionOpened = true;
assert !mSessionIds.containsKey(session);
}
MediaDrm.KeyRequest request = null;
request = getKeyRequest(session, initData, mime);
if (request == null) {
if (newSessionOpened) {
closeSession(session);
}
onSessionError(sessionId);
return;
}
onSessionCreated(sessionId, getWebSessionId(session));
onSessionMessage(sessionId, request);
if (newSessionOpened) {
Log.d(TAG, "createSession(): Session " + getWebSessionId(session) +
" (" + sessionId + ") created.");
}
mSessionIds.put(session, sessionId);
mSessionMimeTypes.put(session, mime);
} catch (android.media.NotProvisionedException e) {
Log.e(TAG, "Device not provisioned", e);
if (newSessionOpened) {
closeSession(session);
}
savePendingCreateSessionData(sessionId, initData, mime);
startProvisioning();
}
}
/**
* Returns whether |sessionId| is a valid key session, excluding the media
* crypto session in multi-session mode.
*
* @param sessionId Crypto session Id.
*/
private boolean sessionExists(ByteBuffer session) {
if (mMediaCryptoSession == null) {
assert mSessionIds.isEmpty();
Log.e(TAG, "Session doesn't exist because media crypto session is not created.");
return false;
}
assert mSessionIds.containsKey(mMediaCryptoSession);
if (mSingleSessionMode) {
return mMediaCryptoSession.equals(session);
}
return !session.equals(mMediaCryptoSession) && mSessionIds.containsKey(session);
}
/**
* Cancel a key request for a session Id.
*
* @param sessionId Reference ID of session to be released.
*/
@CalledByNative
private void releaseSession(int sessionId) {
Log.d(TAG, "releaseSession(): " + sessionId);
if (mMediaDrm == null) {
Log.e(TAG, "releaseSession() called when MediaDrm is null.");
return;
}
ByteBuffer session = getSession(sessionId);
if (session == null) {
Log.e(TAG, "Invalid sessionId in releaseSession.");
onSessionError(sessionId);
return;
}
mMediaDrm.removeKeys(session.array());
// We don't close the media crypto session in single session mode.
if (!mSingleSessionMode) {
Log.d(TAG, "Session " + sessionId + "closed.");
closeSession(session);
mSessionIds.remove(session);
onSessionClosed(sessionId);
}
}
/**
* Add a key for a session Id.
*
* @param sessionId Reference ID of session to be updated.
* @param key Response data from the server.
*/
@CalledByNative
private void updateSession(int sessionId, byte[] key) {
Log.d(TAG, "updateSession(): " + sessionId);
if (mMediaDrm == null) {
Log.e(TAG, "updateSession() called when MediaDrm is null.");
return;
}
// TODO(xhwang): We should be able to DCHECK this when WD EME is implemented.
ByteBuffer session = getSession(sessionId);
if (!sessionExists(session)) {
Log.e(TAG, "Invalid session in updateSession.");
onSessionError(sessionId);
return;
}
try {
try {
mMediaDrm.provideKeyResponse(session.array(), key);
} catch (java.lang.IllegalStateException e) {
// This is not really an exception. Some error code are incorrectly
// reported as an exception.
// TODO(qinmin): remove this exception catch when b/10495563 is fixed.
Log.e(TAG, "Exception intentionally caught when calling provideKeyResponse()", e);
}
onSessionReady(sessionId);
Log.d(TAG, "Key successfully added for session " + sessionId);
return;
} catch (android.media.NotProvisionedException e) {
// TODO(xhwang): Should we handle this?
Log.e(TAG, "failed to provide key response", e);
} catch (android.media.DeniedByServerException e) {
Log.e(TAG, "failed to provide key response", e);
}
onSessionError(sessionId);
release();
}
/**
* Return the security level of this DRM object.
*/
@CalledByNative
private String getSecurityLevel() {
if (mMediaDrm == null) {
Log.e(TAG, "getSecurityLevel() called when MediaDrm is null.");
return null;
}
return mMediaDrm.getPropertyString("securityLevel");
}
private void startProvisioning() {
Log.d(TAG, "startProvisioning");
assert mMediaDrm != null;
assert !mProvisioningPending;
mProvisioningPending = true;
MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest();
PostRequestTask postTask = new PostRequestTask(request.getData());
postTask.execute(request.getDefaultUrl());
}
/**
* Called when the provision response is received.
*
* @param response Response data from the provision server.
*/
private void onProvisionResponse(byte[] response) {
Log.d(TAG, "onProvisionResponse()");
assert mProvisioningPending;
mProvisioningPending = false;
// If |mMediaDrm| is released, there is no need to callback native.
if (mMediaDrm == null) {
return;
}
boolean success = provideProvisionResponse(response);
if (mResetDeviceCredentialsPending) {
nativeOnResetDeviceCredentialsCompleted(mNativeMediaDrmBridge, success);
mResetDeviceCredentialsPending = false;
}
if (success) {
resumePendingOperations();
}
}
/**
* Provide the provisioning response to MediaDrm.
* @returns false if the response is invalid or on error, true otherwise.
*/
boolean provideProvisionResponse(byte[] response) {
if (response == null || response.length == 0) {
Log.e(TAG, "Invalid provision response.");
return false;
}
try {
mMediaDrm.provideProvisionResponse(response);
return true;
} catch (android.media.DeniedByServerException e) {
Log.e(TAG, "failed to provide provision response", e);
} catch (java.lang.IllegalStateException e) {
Log.e(TAG, "failed to provide provision response", e);
}
return false;
}
private void onSessionCreated(final int sessionId, final String webSessionId) {
mHandler.post(new Runnable(){
@Override
public void run() {
nativeOnSessionCreated(mNativeMediaDrmBridge, sessionId, webSessionId);
}
});
}
private void onSessionMessage(final int sessionId, final MediaDrm.KeyRequest request) {
mHandler.post(new Runnable(){
@Override
public void run() {
nativeOnSessionMessage(mNativeMediaDrmBridge, sessionId,
request.getData(), request.getDefaultUrl());
}
});
}
private void onSessionReady(final int sessionId) {
mHandler.post(new Runnable() {
@Override
public void run() {
nativeOnSessionReady(mNativeMediaDrmBridge, sessionId);
}
});
}
private void onSessionClosed(final int sessionId) {
mHandler.post(new Runnable() {
@Override
public void run() {
nativeOnSessionClosed(mNativeMediaDrmBridge, sessionId);
}
});
}
private void onSessionError(final int sessionId) {
// TODO(qinmin): pass the error code to native.
mHandler.post(new Runnable() {
@Override
public void run() {
nativeOnSessionError(mNativeMediaDrmBridge, sessionId);
}
});
}
private String getWebSessionId(ByteBuffer session) {
String webSessionId = null;
try {
webSessionId = new String(session.array(), "UTF-8");
} catch (java.io.UnsupportedEncodingException e) {
Log.e(TAG, "getWebSessionId failed", e);
} catch (java.lang.NullPointerException e) {
Log.e(TAG, "getWebSessionId failed", e);
}
return webSessionId;
}
private class MediaDrmListener implements MediaDrm.OnEventListener {
@Override
public void onEvent(
MediaDrm mediaDrm, byte[] session_array, int event, int extra, byte[] data) {
if (session_array == null) {
Log.e(TAG, "MediaDrmListener: Null session.");
return;
}
ByteBuffer session = ByteBuffer.wrap(session_array);
if (!sessionExists(session)) {
Log.e(TAG, "MediaDrmListener: Invalid session.");
return;
}
Integer sessionId = mSessionIds.get(session);
if (sessionId == null || sessionId == INVALID_SESSION_ID) {
Log.e(TAG, "MediaDrmListener: Invalid session ID.");
return;
}
switch(event) {
case MediaDrm.EVENT_PROVISION_REQUIRED:
Log.d(TAG, "MediaDrm.EVENT_PROVISION_REQUIRED");
break;
case MediaDrm.EVENT_KEY_REQUIRED:
Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED");
if (mProvisioningPending) {
return;
}
String mime = mSessionMimeTypes.get(session);
MediaDrm.KeyRequest request = null;
try {
request = getKeyRequest(session, data, mime);
} catch (android.media.NotProvisionedException e) {
Log.e(TAG, "Device not provisioned", e);
startProvisioning();
return;
}
if (request != null) {
onSessionMessage(sessionId, request);
} else {
onSessionError(sessionId);
}
break;
case MediaDrm.EVENT_KEY_EXPIRED:
Log.d(TAG, "MediaDrm.EVENT_KEY_EXPIRED");
onSessionError(sessionId);
break;
case MediaDrm.EVENT_VENDOR_DEFINED:
Log.d(TAG, "MediaDrm.EVENT_VENDOR_DEFINED");
assert false; // Should never happen.
break;
default:
Log.e(TAG, "Invalid DRM event " + event);
return;
}
}
}
private class PostRequestTask extends AsyncTask<String, Void, Void> {
private static final String TAG = "PostRequestTask";
private byte[] mDrmRequest;
private byte[] mResponseBody;
public PostRequestTask(byte[] drmRequest) {
mDrmRequest = drmRequest;
}
@Override
protected Void doInBackground(String... urls) {
mResponseBody = postRequest(urls[0], mDrmRequest);
if (mResponseBody != null) {
Log.d(TAG, "response length=" + mResponseBody.length);
}
return null;
}
private byte[] postRequest(String url, byte[] drmRequest) {
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url + "&signedRequest=" + new String(drmRequest));
Log.d(TAG, "PostRequest:" + httpPost.getRequestLine());
try {
// Add data
httpPost.setHeader("Accept", "*/*");
httpPost.setHeader("User-Agent", "Widevine CDM v1.0");
httpPost.setHeader("Content-Type", "application/json");
// Execute HTTP Post Request
HttpResponse response = httpClient.execute(httpPost);
byte[] responseBody;
int responseCode = response.getStatusLine().getStatusCode();
if (responseCode == 200) {
responseBody = EntityUtils.toByteArray(response.getEntity());
} else {
Log.d(TAG, "Server returned HTTP error code " + responseCode);
return null;
}
return responseBody;
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void v) {
onProvisionResponse(mResponseBody);
}
}
private native void nativeOnMediaCryptoReady(long nativeMediaDrmBridge);
private native void nativeOnSessionCreated(long nativeMediaDrmBridge, int sessionId,
String webSessionId);
private native void nativeOnSessionMessage(long nativeMediaDrmBridge, int sessionId,
byte[] message, String destinationUrl);
private native void nativeOnSessionReady(long nativeMediaDrmBridge, int sessionId);
private native void nativeOnSessionClosed(long nativeMediaDrmBridge, int sessionId);
private native void nativeOnSessionError(long nativeMediaDrmBridge, int sessionId);
private native void nativeOnResetDeviceCredentialsCompleted(
long nativeMediaDrmBridge, boolean success);
}