blob: 59044e447ade33e6b2e33720e66e05be985138ec [file] [log] [blame]
// 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.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 (mMediaCryptoSessionId) 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 and it's associated mime type is always null.
// 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.
// - Each generateKeyRequest() 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 generateKeyRequest() if previous generateKeyRequest() 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 MediaDrm mMediaDrm;
private long mNativeMediaDrmBridge;
private UUID mSchemeUUID;
private Handler mHandler;
// In this mode, we only open one session, i.e. mMediaCryptoSessionId, and
// mSessionIds is always empty.
private boolean mSingleSessionMode;
// A session only for the purpose of creating a MediaCrypto object.
// This session is opened when generateKeyRequest is called for the first
// time.
// - In multiple session mode, all following generateKeyRequest() 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 generateKeyRequest() calls use the same
// media crypto session. When generateKeyRequest() is called with a new
// initData, previously added keys may not be available anymore.
private String mMediaCryptoSessionId;
private MediaCrypto mMediaCrypto;
// The map of all opened sessions to their mime types.
private HashMap<String, String> mSessionIds;
private ArrayDeque<PendingGkrData> mPendingGkrDataQueue;
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 generateKeyRequest().
*/
private static class PendingGkrData {
private final byte[] mInitData;
private final String mMimeType;
private PendingGkrData(byte[] initData, String mimeType) {
mInitData = initData;
mMimeType = mimeType;
}
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);
}
private MediaDrmBridge(UUID schemeUUID, String securityLevel, long nativeMediaDrmBridge,
boolean singleSessionMode) throws android.media.UnsupportedSchemeException {
mSchemeUUID = schemeUUID;
mMediaDrm = new MediaDrm(schemeUUID);
mNativeMediaDrmBridge = nativeMediaDrmBridge;
mHandler = new Handler();
mSingleSessionMode = singleSessionMode;
mSessionIds = new HashMap<String, String>();
mPendingGkrDataQueue = new ArrayDeque<PendingGkrData>();
mResetDeviceCredentialsPending = false;
mProvisioningPending = false;
mMediaDrm.setOnEventListener(new MediaDrmListener());
mMediaDrm.setPropertyString(PRIVACY_MODE, ENABLE);
if (!mSingleSessionMode) {
mMediaDrm.setPropertyString(SESSION_SHARING, ENABLE);
}
String currentSecurityLevel = mMediaDrm.getPropertyString(SECURITY_LEVEL);
Log.e(TAG, "Security level: current " + currentSecurityLevel + ", new " + securityLevel);
if (!securityLevel.equals(currentSecurityLevel)) {
mMediaDrm.setPropertyString(SECURITY_LEVEL, securityLevel);
}
// We could open a MediaCrypto session here to support faster start of
// clear lead (no need to wait for generateKeyRequest()). 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(mMediaCryptoSessionId == null);
assert(mMediaCrypto == null);
// Open media crypto session.
mMediaCryptoSessionId = openSession();
if (mMediaCryptoSessionId == null) {
Log.e(TAG, "Cannot create MediaCrypto Session.");
return false;
}
Log.d(TAG, "MediaCrypto Session created: " + mMediaCryptoSessionId);
// Create MediaCrypto object.
try {
if (MediaCrypto.isCryptoSchemeSupported(mSchemeUUID)) {
final byte[] mediaCryptoSession = mMediaCryptoSessionId.getBytes("UTF-8");
mMediaCrypto = new MediaCrypto(mSchemeUUID, mediaCryptoSession);
assert(mMediaCrypto != null);
Log.d(TAG, "MediaCrypto successfully created!");
mSessionIds.put(mMediaCryptoSessionId, null);
// Notify the native code that MediaCrypto is ready.
nativeOnMediaCryptoReady(mNativeMediaDrmBridge);
return true;
} else {
Log.e(TAG, "Cannot create MediaCrypto for unsupported scheme.");
}
} catch (java.io.UnsupportedEncodingException e) {
Log.e(TAG, "Cannot create MediaCrypto", e);
} catch (android.media.MediaCryptoException e) {
Log.e(TAG, "Cannot create MediaCrypto", e);
}
release();
return false;
}
/**
* Open a new session and return the session ID string.
*
* @return null if unexpected error happened.
*/
private String openSession() throws android.media.NotProvisionedException {
assert(mMediaDrm != null);
String sessionId = null;
try {
final byte[] session = mMediaDrm.openSession();
sessionId = new String(session, "UTF-8");
} catch (java.lang.RuntimeException e) { // TODO(xhwang): Drop this?
Log.e(TAG, "Cannot open a new session", e);
release();
} catch (java.io.UnsupportedEncodingException e) {
Log.e(TAG, "Cannot open a new session", e);
release();
}
return sessionId;
}
/**
* Close a session.
*
* @param sesstionIdString ID of the session to be closed.
*/
private void closeSession(String sesstionIdString) {
assert(mMediaDrm != null);
try {
final byte[] session = sesstionIdString.getBytes("UTF-8");
mMediaDrm.closeSession(session);
} catch (java.io.UnsupportedEncodingException e) {
Log.e(TAG, "Failed to close session", e);
}
}
/**
* 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, String securityLevel, 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 media_drm_bridge = null;
try {
media_drm_bridge = new MediaDrmBridge(
cryptoScheme, securityLevel, nativeMediaDrmBridge, singleSessionMode);
} 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 media_drm_bridge;
}
/**
* 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.
mPendingGkrDataQueue.clear();
mPendingGkrDataQueue = null;
for (String sessionId : mSessionIds.keySet()) {
closeSession(sessionId);
}
mSessionIds.clear();
mSessionIds = null;
// This session was closed in the "for" loop above.
mMediaCryptoSessionId = null;
if (mMediaCrypto != null) {
mMediaCrypto.release();
mMediaCrypto = null;
}
if (mMediaDrm != null) {
mMediaDrm.release();
mMediaDrm = null;
}
}
/**
* Get a key request and post an asynchronous task to the native side
* with the response message upon success, or with the key error if
* unexpected error happened.
*
* @param sessionId ID of the 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 whether a key request is successfully obtained.
*/
private boolean getKeyRequest(String sessionId, byte[] data, String mime)
throws android.media.NotProvisionedException {
assert(mMediaDrm != null);
assert(mMediaCrypto != null);
assert(!mProvisioningPending);
try {
final byte[] session = sessionId.getBytes("UTF-8");
HashMap<String, String> optionalParameters = new HashMap<String, String>();
final MediaDrm.KeyRequest request = mMediaDrm.getKeyRequest(
session, data, mime, MediaDrm.KEY_TYPE_STREAMING, optionalParameters);
Log.e(TAG, "Got key request successfully.");
onKeyMessage(sessionId, request.getData(), request.getDefaultUrl());
return true;
} catch (java.io.UnsupportedEncodingException e) {
Log.e(TAG, "Cannot get key request", e);
}
onKeyError(sessionId);
release();
return false;
}
/**
* Save |initData| and |mime| to |mPendingGkrDataQueue| so that we can
* resume the generateKeyRequest() call later.
*/
private void savePendingGkrData(byte[] initData, String mime) {
Log.d(TAG, "savePendingGkrData()");
mPendingGkrDataQueue.offer(new PendingGkrData(initData, mime));
}
/**
* Process all pending generateKeyRequest() calls synchronously.
*/
private void processPendingGkrData() {
Log.d(TAG, "processPendingGkrData()");
assert(mMediaDrm != null);
// Check mMediaDrm != null because error may happen in generateKeyRequest().
// Check !mProvisioningPending because NotProvisionedException may be
// thrown in generateKeyRequest().
while (mMediaDrm != null && !mProvisioningPending && !mPendingGkrDataQueue.isEmpty()) {
PendingGkrData pendingGkrData = mPendingGkrDataQueue.poll();
byte[] initData = pendingGkrData.initData();
String mime = pendingGkrData.mimeType();
generateKeyRequest(initData, mime);
}
}
/**
* Process pending operations asynchrnously.
*/
private void resumePendingOperations() {
mHandler.post(new Runnable(){
public void run() {
processPendingGkrData();
}
});
}
/**
* Generate a key request with |initData| and |mime|, and post an
* asynchronous task to the native side with the key request or a key error.
* In multiple session mode, a new session will be open. In single session
* mode, the mMediaCryptoSessionId will be used.
*
* @param initData Data needed to generate the key request.
* @param mime Mime type.
*/
@CalledByNative
private void generateKeyRequest(byte[] initData, String mime) {
Log.d(TAG, "generateKeyRequest()");
if (mMediaDrm == null) {
Log.e(TAG, "generateKeyRequest() called when MediaDrm is null.");
return;
}
if (mProvisioningPending) {
assert(mMediaCrypto == null);
savePendingGkrData(initData, mime);
return;
}
boolean newSessionOpened = false;
String sessionId = null;
try {
// Create MediaCrypto if necessary.
if (mMediaCrypto == null && !createMediaCrypto()) {
onKeyError(null);
return;
}
assert(mMediaCrypto != null);
assert(mSessionIds.containsKey(mMediaCryptoSessionId));
if (mSingleSessionMode) {
sessionId = mMediaCryptoSessionId;
if (mSessionIds.get(sessionId) == null) {
// Set |mime| when we call generateKeyRequest() for the first time.
mSessionIds.put(sessionId, mime);
} else if (!mSessionIds.get(sessionId).equals(mime)) {
Log.e(TAG, "Only one mime type is supported in single session mode.");
onKeyError(sessionId);
return;
}
} else {
sessionId = openSession();
if (sessionId == null) {
Log.e(TAG, "Cannot open session in generateKeyRequest().");
onKeyError(null);
return;
}
newSessionOpened = true;
assert(!mSessionIds.containsKey(sessionId));
}
// KeyMessage or KeyError is fired in getKeyRequest().
if (!getKeyRequest(sessionId, initData, mime)) {
Log.e(TAG, "Cannot get key request in generateKeyRequest().");
return;
}
if (newSessionOpened) {
Log.e(TAG, "generateKeyRequest(): Session " + sessionId + " created.");
mSessionIds.put(sessionId, mime);
return;
}
} catch (android.media.NotProvisionedException e) {
Log.e(TAG, "Device not provisioned", e);
if (newSessionOpened) {
closeSession(sessionId);
}
savePendingGkrData(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(String sessionId) {
if (mMediaCryptoSessionId == null) {
assert(mSessionIds.isEmpty());
Log.e(TAG, "Session doesn't exist because media crypto session is not created.");
return false;
}
assert(mSessionIds.containsKey(mMediaCryptoSessionId));
if (mSingleSessionMode) {
return mMediaCryptoSessionId.equals(sessionId);
}
return !mMediaCryptoSessionId.equals(sessionId) && mSessionIds.containsKey(sessionId);
}
/**
* Cancel a key request for a session Id.
*
* @param sessionId Crypto session Id.
*/
@CalledByNative
private void cancelKeyRequest(String sessionId) {
Log.d(TAG, "cancelKeyRequest(): " + sessionId);
if (mMediaDrm == null) {
Log.e(TAG, "cancelKeyRequest() called when MediaDrm is null.");
return;
}
if (!sessionExists(sessionId)) {
Log.e(TAG, "Invalid session in cancelKeyRequest.");
onKeyError(sessionId);
return;
}
try {
final byte[] session = sessionId.getBytes("UTF-8");
mMediaDrm.removeKeys(session);
} catch (java.io.UnsupportedEncodingException e) {
Log.e(TAG, "Cannot cancel key request", e);
}
// We don't close the media crypto session in single session mode.
if (!mSingleSessionMode) {
closeSession(sessionId);
mSessionIds.remove(sessionId);
Log.d(TAG, "Session " + sessionId + "closed.");
}
}
/**
* Add a key for a session Id.
*
* @param sessionId Crypto session Id.
* @param key Response data from the server.
*/
@CalledByNative
private void addKey(String sessionId, byte[] key) {
Log.d(TAG, "addKey(): " + sessionId);
if (mMediaDrm == null) {
Log.e(TAG, "addKey() called when MediaDrm is null.");
return;
}
// TODO(xhwang): We should be able to DCHECK this when WD EME is implemented.
if (!sessionExists(sessionId)) {
Log.e(TAG, "Invalid session in addKey.");
onKeyError(sessionId);
return;
}
try {
final byte[] session = sessionId.getBytes("UTF-8");
try {
mMediaDrm.provideKeyResponse(session, 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);
}
onKeyAdded(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);
} catch (java.io.UnsupportedEncodingException e) {
Log.e(TAG, "failed to provide key response", e);
}
onKeyError(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 onKeyMessage(
final String sessionId, final byte[] message, final String destinationUrl) {
mHandler.post(new Runnable(){
public void run() {
nativeOnKeyMessage(mNativeMediaDrmBridge, sessionId, message, destinationUrl);
}
});
}
private void onKeyAdded(final String sessionId) {
mHandler.post(new Runnable() {
public void run() {
nativeOnKeyAdded(mNativeMediaDrmBridge, sessionId);
}
});
}
private void onKeyError(final String sessionId) {
// TODO(qinmin): pass the error code to native.
mHandler.post(new Runnable() {
public void run() {
nativeOnKeyError(mNativeMediaDrmBridge, sessionId);
}
});
}
private String GetSessionId(byte[] session) {
String sessionId = null;
try {
sessionId = new String(session, "UTF-8");
} catch (java.io.UnsupportedEncodingException e) {
Log.e(TAG, "GetSessionId failed", e);
} catch (java.lang.NullPointerException e) {
Log.e(TAG, "GetSessionId failed", e);
}
return sessionId;
}
private class MediaDrmListener implements MediaDrm.OnEventListener {
@Override
public void onEvent(MediaDrm mediaDrm, byte[] session, int event, int extra, byte[] data) {
String sessionId = null;
switch(event) {
case MediaDrm.EVENT_PROVISION_REQUIRED:
// This is handled by the handler of NotProvisionedException.
Log.d(TAG, "MediaDrm.EVENT_PROVISION_REQUIRED");
break;
case MediaDrm.EVENT_KEY_REQUIRED:
Log.d(TAG, "MediaDrm.EVENT_KEY_REQUIRED");
sessionId = GetSessionId(session);
if (sessionId != null && !mProvisioningPending) {
assert(sessionExists(sessionId));
String mime = mSessionIds.get(sessionId);
try {
getKeyRequest(sessionId, data, mime);
} catch (android.media.NotProvisionedException e) {
Log.e(TAG, "Device not provisioned", e);
startProvisioning();
}
}
break;
case MediaDrm.EVENT_KEY_EXPIRED:
Log.d(TAG, "MediaDrm.EVENT_KEY_EXPIRED");
sessionId = GetSessionId(session);
if (sessionId != null) {
onKeyError(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 " + (int)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 nativeOnKeyMessage(long nativeMediaDrmBridge, String sessionId,
byte[] message, String destinationUrl);
private native void nativeOnKeyAdded(long nativeMediaDrmBridge, String sessionId);
private native void nativeOnKeyError(long nativeMediaDrmBridge, String sessionId);
private native void nativeOnResetDeviceCredentialsCompleted(
long nativeMediaDrmBridge, boolean success);
}