Update java MediaDrm API test to cover more of the API

bug: 8620943
Change-Id: I72b690cfcd838064a470042e0943a711cc536207
This commit is contained in:
Jeff Tinker
2013-04-25 05:34:39 -07:00
parent b5a782bdb2
commit f702e50919
3 changed files with 391 additions and 180 deletions

View File

@@ -0,0 +1,137 @@
package com.widevine.test;
import android.media.MediaDrm;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import android.os.AsyncTask;
import android.util.Log;
public class KeyRequester {
private final String TAG = "KeyRequester";
private byte[] mPssh;
private String mServerUrl;
public KeyRequester(byte[] pssh, String url) {
mPssh = pssh;
mServerUrl = url;
}
public void doTransact(MediaDrm drm, byte[] sessionId) {
MediaDrm.KeyRequest drmRequest;
drmRequest = drm.getKeyRequest(sessionId, mPssh, "video/avc",
MediaDrm.KEY_TYPE_STREAMING, null);
PostRequestTask postTask = new PostRequestTask(drmRequest.getData());
postTask.execute(mServerUrl);
// wait for post task to complete
byte[] responseBody;
long startTime = System.currentTimeMillis();
do {
responseBody = postTask.getResponseBody();
} while (responseBody == null && System.currentTimeMillis() - startTime < 5000);
if (responseBody == null) {
Log.d(TAG, "No response from license server!");
} else {
byte[] drmResponse = parseResponseBody(responseBody);
drm.provideKeyResponse(sessionId, drmResponse);
}
}
private class PostRequestTask extends AsyncTask<String, Void, Void> {
private final String TAG = "PostRequestTask";
private byte[] mDrmRequest;
private byte[] mResponseBody;
public PostRequestTask(byte[] drmRequest) {
mDrmRequest = drmRequest;
}
protected Void doInBackground(String... urls) {
mResponseBody = postRequest(urls[0], mDrmRequest);
Log.d(TAG, "response length=" + mResponseBody.length);
return null;
}
public byte[] getResponseBody() {
return mResponseBody;
}
private byte[] postRequest(String url, byte[] drmRequest) {
Log.d(TAG, "PostRequest url=" + url);
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
try {
// Add data
ByteArrayEntity entity = new ByteArrayEntity(drmRequest);
entity.setChunked(true);
httppost.setEntity(entity);
httppost.setHeader("User-Agent", "Widevine CDM v1.0");
// 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;
}
}
// validate the response body and return the drmResponse blob
private byte[] parseResponseBody(byte[] responseBody) {
String bodyString = null;
try {
bodyString = new String(responseBody, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (bodyString == null) {
return null;
}
if (!bodyString.startsWith("GLS/")) {
Log.e(TAG, "Invalid response from server, expected GLS/");
return null;
}
if (!bodyString.startsWith("GLS/1.")) {
Log.e(TAG, "Invalid server version, expected 1.x");
return null;
}
int drmMessageOffset = bodyString.indexOf("\r\n\r\n");
if (drmMessageOffset == -1) {
Log.e(TAG, "Invalid server response, could not locate drm message");
return null;
}
return Arrays.copyOfRange(responseBody, drmMessageOffset + 4, responseBody.length);
}
}

View File

@@ -6,141 +6,42 @@ package com.widevine.test;
import android.app.TabActivity;
import android.os.Bundle;
import android.os.AsyncTask;
import android.media.MediaDrm;
import android.media.MediaDrmException;
import android.media.MediaCrypto;
import android.media.MediaCodec;
import android.media.MediaCryptoException;
import android.media.MediaCodec.CryptoException;
import android.media.MediaCodecList;
import android.media.MediaCodec.CryptoInfo;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaCryptoException;
import android.media.MediaCodec.CryptoException;
import android.util.Log;
import java.util.concurrent.TimeUnit;
import java.util.UUID;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Iterator;
import java.util.HashMap;
import java.util.Random;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.lang.Exception;
import java.lang.InterruptedException;
import java.security.MessageDigest;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import java.security.NoSuchAlgorithmException;
public class MediaDrmAPITest extends TabActivity {
private final String TAG = "MediaDrmAPITest";
private class PostRequestTask extends AsyncTask<String, Void, Void> {
private byte[] mDrmRequest;
private byte[] mResponseBody;
public PostRequestTask(byte[] drmRequest) {
mDrmRequest = drmRequest;
}
protected Void doInBackground(String... urls) {
mResponseBody = postRequest(urls[0], mDrmRequest);
Log.d(TAG, "response length=" + mResponseBody.length);
return null;
}
public byte[] getResponseBody() {
return mResponseBody;
}
}
private byte[] postRequest(String url, byte[] drmRequest) {
Log.d(TAG, "PostRequest url=" + url);
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
try {
// Add data
ByteArrayEntity entity = new ByteArrayEntity(drmRequest);
entity.setChunked(true);
httppost.setEntity(entity);
httppost.setHeader("User-Agent", "Widevine CDM v1.0");
// 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;
}
private static byte[] hex2ba(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
// validate the response body and return the drmResponse blob
private byte[] parseResponseBody(byte[] responseBody) {
String bodyString = null;
try {
bodyString = new String(responseBody, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (bodyString == null) {
return null;
}
if (!bodyString.startsWith("GLS/")) {
Log.e(TAG, "Invalid response from server, expected GLS/");
return null;
}
if (!bodyString.startsWith("GLS/1.")) {
Log.e(TAG, "Invalid server version, expected 1.x");
return null;
}
int drmMessageOffset = bodyString.indexOf("\r\n\r\n");
if (drmMessageOffset == -1) {
Log.e(TAG, "Invalid server response, could not locate drm message");
return null;
}
return Arrays.copyOfRange(responseBody, drmMessageOffset + 4, responseBody.length);
}
static final UUID kWidevineScheme = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
static final String kClientAuth = "?source=YOUTUBE&video_id=EGHC6OHNbOo&oauth=ya.gtsqawidevine";
static final String kServerUrl = "https://jmt17.google.com/video-dev/license/GetCencLicense";
static final String kKeyServerUrl = "https://jmt17.google.com/video-dev/license/GetCencLicense";
static final String kPort = "80";
static final byte[] kKeyId = hex2ba(// pssh data
"08011210e02562e04cd55351b14b3d748d36ed8e");
static final byte[] kPssh = hex2ba(// pssh data
"08011210e02562e04cd55351b14b3d748d36ed8e");
private boolean mTestFailed;
/** Called when the activity is first created. */
@Override
@@ -149,64 +50,120 @@ public class MediaDrmAPITest extends TabActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
listDecoders();
mTestFailed = false;
testWidevineSchemeSupported();
testProvisioning();
testProperties();
testQueryKeyStatus();
testClearContentNoKeys();
testEncryptedContent();
if (mTestFailed) {
Log.e(TAG, "TEST FAILED!");
} else {
Log.e(TAG, "TEST SUCCESS!");
}
}
private void testWidevineSchemeSupported() {
if (!MediaDrm.isCryptoSchemeSupported(kWidevineScheme)) {
Log.e(TAG, "testWidevineSchemeSupported failed");
finish();
}
}
private void testProperties() {
MediaDrm drm = null;
try {
drm = new MediaDrm(kWidevineScheme);
} catch (MediaDrmException e) {
Log.e(TAG, "test failed due to exception: " + e.getMessage());
e.printStackTrace();
finish();
}
Log.i(TAG, "vendor: " + drm.getPropertyString(MediaDrm.PROPERTY_VENDOR));
Log.i(TAG, "version: " + drm.getPropertyString(MediaDrm.PROPERTY_VERSION));
Log.i(TAG, "description: " + drm.getPropertyString(MediaDrm.PROPERTY_DESCRIPTION));
Log.i(TAG, "deviceId: " + Arrays.toString(drm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)));
Log.i(TAG, "algorithms: " + drm.getPropertyString(MediaDrm.PROPERTY_ALGORITHMS));
// widevine-specific properties
Log.i(TAG, "security level: " + drm.getPropertyString("securityLevel"));
Log.i(TAG, "system ID: " + drm.getPropertyString("systemId"));
}
private void testProvisioning() {
MediaDrm drm = null;
try {
drm = new MediaDrm(kWidevineScheme);
} catch (MediaDrmException e) {
Log.e(TAG, "test failed due to exception: " + e.getMessage());
e.printStackTrace();
finish();
}
ProvisionRequester provisionRequester = new ProvisionRequester();
provisionRequester.doTransact(drm);
}
private void testQueryKeyStatus() {
MediaDrm drm = null;
try {
drm = new MediaDrm(kWidevineScheme);
} catch (MediaDrmException e) {
Log.e(TAG, "test failed due to exception: " + e.getMessage());
e.printStackTrace();
finish();
}
byte[] sessionId = drm.openSession();
KeyRequester keyRequester = new KeyRequester(kPssh, kKeyServerUrl + ":" + kPort + kClientAuth);
keyRequester.doTransact(drm, sessionId);
Log.i(TAG, "Query Key Status:");
HashMap<String, String> keyStatus = drm.queryKeyStatus(sessionId);
Iterator<String> iterator = keyStatus.keySet().iterator();
while (iterator.hasNext()) {
String name = iterator.next();
Log.i(TAG, "\t" + name + " = " + keyStatus.get(name));
}
drm.closeSession(sessionId);
}
private void testEncryptedContent() {
MediaDrm drm = null;
try {
MediaDrm drm = new MediaDrm(kWidevineScheme);
byte[] sessionId = drm.openSession();
doLicenseExchange(drm, sessionId);
testDecrypt(sessionId);
drm.closeSession(sessionId);
} catch (Exception e) {
drm = new MediaDrm(kWidevineScheme);
} catch (MediaDrmException e) {
Log.e(TAG, "test failed due to exception: " + e.getMessage());
e.printStackTrace();
finish();
}
byte[] sessionId = drm.openSession();
KeyRequester keyRequester = new KeyRequester(kPssh, kKeyServerUrl + ":" + kPort + kClientAuth);
keyRequester.doTransact(drm, sessionId);
testDecrypt(sessionId);
drm.closeSession(sessionId);
}
private void testClearContentNoKeys() {
MediaDrm drm = null;
try {
MediaDrm drm = new MediaDrm(kWidevineScheme);
byte[] sessionId = drm.openSession();
testClear(sessionId);
drm.closeSession(sessionId);
} catch (Exception e) {
drm = new MediaDrm(kWidevineScheme);
} catch (MediaDrmException e) {
Log.e(TAG, "test failed due to exception: " + e.getMessage());
e.printStackTrace();
finish();
}
byte[] sessionId = drm.openSession();
testClear(sessionId);
drm.closeSession(sessionId);
}
public void doLicenseExchange(MediaDrm drm, byte[] sessionId) throws Exception {
MediaDrm.KeyRequest drmRequest;
drmRequest = drm.getKeyRequest(sessionId, kKeyId, "video/avc",
MediaDrm.KEY_TYPE_STREAMING, null);
PostRequestTask postTask = new PostRequestTask(drmRequest.getData());
postTask.execute(kServerUrl + ":" + kPort + kClientAuth);
// wait for post task to complete
byte[] responseBody;
long startTime = System.currentTimeMillis();
do {
responseBody = postTask.getResponseBody();
} while (responseBody == null && System.currentTimeMillis() - startTime < 5000);
if (responseBody == null) {
Log.d(TAG, "No response from license server!");
} else {
byte[] drmResponse = parseResponseBody(responseBody);
drm.provideKeyResponse(sessionId, drmResponse);
}
}
private byte[] getTestModeSessionId(byte[] sessionId) {
String testMode = new String("test_mode");
byte[] testModeSessionId = new byte[sessionId.length + testMode.length()];
@@ -219,16 +176,25 @@ public class MediaDrmAPITest extends TabActivity {
return testModeSessionId;
}
private void sleep(int msec) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
// do minimal codec setup to pass an encrypted buffer down the stack to see if it gets
// decrypted correctly.
public void testDecrypt(byte[] sessionId) throws Exception {
public void testDecrypt(byte[] sessionId) {
Log.i(TAG, "testDecrypt");
MediaCrypto crypto;
MediaCrypto crypto = null;
try {
crypto = new MediaCrypto(kWidevineScheme, getTestModeSessionId(sessionId));
} catch (MediaCryptoException e) {
throw e;
Log.e(TAG, "test failed due to exception: " + e.getMessage());
e.printStackTrace();
finish();
}
String mime = "video/avc";
@@ -251,13 +217,13 @@ public class MediaDrmAPITest extends TabActivity {
int index;
Log.i(TAG, "waiting for buffer...");
while ((index = codec.dequeueInputBuffer(0 /* timeoutUs */)) < 0) {
Thread.sleep(10);
sleep(10);
}
Log.i(TAG, "Got index " + index);
final int kMaxSubsamplesPerSample = 10;
final int kMaxSampleSize = 128 * 1024;
;
int clearSizes[] = new int[kMaxSubsamplesPerSample];
int encryptedSizes[] = new int[kMaxSubsamplesPerSample];
@@ -286,16 +252,22 @@ public class MediaDrmAPITest extends TabActivity {
MediaCodec.CRYPTO_MODE_AES_CTR);
try {
Log.i(TAG,"Sending " + sampleSize + " bytes, numSubSamples=" + numSubSamples);
// Log.i(TAG,"Sending " + sampleSize + " bytes, numSubSamples=" + numSubSamples);
codec.queueSecureInputBuffer(index, 0 /* offset */, info,
0 /* sampleTime */, 0 /* flags */);
} catch (CryptoException e) {
Log.i(TAG,"Checking " + sampleSize + " bytes");
// Log.i(TAG,"Checking " + sampleSize + " bytes");
// in test mode, the WV CryptoPlugin throws a CryptoException where the
// message string contains a SHA256 hash of the decrypted data, for verification
// purposes.
MessageDigest digest = MessageDigest.getInstance("SHA-256");
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException ex) {
ex.printStackTrace();
finish();
}
byte buf[] = Arrays.copyOf(refBuffer.array(), sampleSize);
byte[] sha256 = digest.digest(buf);
if (Arrays.equals(sha256, hex2ba(e.getMessage()))) {
@@ -306,6 +278,7 @@ public class MediaDrmAPITest extends TabActivity {
for (int i = 0; i < sha256.length; i++) {
System.out.printf("%02x", sha256[i]);
}
mTestFailed = true;
}
}
@@ -345,14 +318,16 @@ public class MediaDrmAPITest extends TabActivity {
// do minimal codec setup to pass a clear buffer down the stack to see if it gets
// passed through correctly.
public void testClear(byte[] sessionId) throws Exception {
public void testClear(byte[] sessionId) {
Log.i(TAG, "testClear");
MediaCrypto crypto;
MediaCrypto crypto = null;
try {
crypto = new MediaCrypto(kWidevineScheme, getTestModeSessionId(sessionId));
} catch (MediaCryptoException e) {
throw e;
Log.e(TAG, "test failed due to exception: " + e.getMessage());
e.printStackTrace();
finish();
}
String mime = "video/avc";
@@ -375,7 +350,7 @@ public class MediaDrmAPITest extends TabActivity {
int index;
Log.i(TAG, "waiting for buffer...");
while ((index = codec.dequeueInputBuffer(0 /* timeoutUs */)) < 0) {
Thread.sleep(10);
sleep(10);
}
Log.i(TAG, "Got index " + index);
@@ -397,7 +372,13 @@ public class MediaDrmAPITest extends TabActivity {
// in test mode, the WV CryptoPlugin throws a CryptoException where the
// message string contains a SHA256 hash of the decrypted data, for verification
// purposes.
MessageDigest digest = MessageDigest.getInstance("SHA-256");
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException ex) {
ex.printStackTrace();
finish();
}
byte[] sha256 = digest.digest(refBuffer.array());
if (Arrays.equals(sha256, hex2ba(e.getMessage()))) {
Log.i(TAG, "sha256: " + e.getMessage() + " matches OK");
@@ -407,6 +388,7 @@ public class MediaDrmAPITest extends TabActivity {
for (int i = 0; i < sha256.length; i++) {
System.out.printf("%02x", sha256[i]);
}
mTestFailed = true;
}
}
}
@@ -437,21 +419,14 @@ public class MediaDrmAPITest extends TabActivity {
return null;
}
private void listDecoders() {
int n = MediaCodecList.getCodecCount();
for (int i = 0; i < n; ++i) {
MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
if (info.isEncoder()) {
continue;
}
String[] supportedTypes = info.getSupportedTypes();
Log.i(TAG, "codec: " + info.getName());
for (int j = 0; j < supportedTypes.length; ++j) {
Log.i(TAG, " type: " + supportedTypes[j]);
}
private static byte[] hex2ba(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}

View File

@@ -0,0 +1,99 @@
package com.widevine.test;
import android.media.MediaDrm;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import android.os.AsyncTask;
import android.util.Log;
public class ProvisionRequester {
private final String TAG = "ProvisionRequester";
public ProvisionRequester() {
}
public void doTransact(MediaDrm drm) {
MediaDrm.ProvisionRequest drmRequest;
drmRequest = drm.getProvisionRequest();
PostRequestTask postTask = new PostRequestTask(drmRequest.getData());
Log.i(TAG, "Attempting to provision from server '" + drmRequest.getDefaultUrl() + "'");
postTask.execute(drmRequest.getDefaultUrl());
// wait for post task to complete
byte[] responseBody;
long startTime = System.currentTimeMillis();
do {
responseBody = postTask.getResponseBody();
} while (responseBody == null && System.currentTimeMillis() - startTime < 5000);
if (responseBody == null) {
Log.d(TAG, "No response from provisioning server!");
} else {
drm.provideProvisionResponse(responseBody);
}
}
private class PostRequestTask extends AsyncTask<String, Void, Void> {
private final String TAG = "PostRequestTask";
private byte[] mDrmRequest;
private byte[] mResponseBody;
public PostRequestTask(byte[] drmRequest) {
mDrmRequest = drmRequest;
}
protected Void doInBackground(String... urls) {
mResponseBody = postRequest(urls[0], mDrmRequest);
Log.d(TAG, "response length=" + mResponseBody.length);
return null;
}
public byte[] getResponseBody() {
return mResponseBody;
}
private byte[] postRequest(String url, byte[] drmRequest) {
Log.d(TAG, "PostRequest url=" + url);
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
try {
// Add data
ByteArrayEntity entity = new ByteArrayEntity(drmRequest);
httppost.setEntity(entity);
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;
}
}
}