Implementing MediaDrm APIs
Change-Id: Ib6eeb9c04c5c5cf1d485f9004cd3e6a1047a1d19
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
new file mode 100644
index 0000000..4561d3f
--- /dev/null
+++ b/media/java/android/media/MediaDrm.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.MediaDrmException;
+import java.lang.ref.WeakReference;
+import java.util.UUID;
+import java.util.HashMap;
+import java.util.List;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * MediaDrm class can be used in conjunction with {@link android.media.MediaCrypto}
+ * to obtain licenses for decoding encrypted media data.
+ *
+ * Crypto schemes are assigned 16 byte UUIDs,
+ * the method {@link #isCryptoSchemeSupported} can be used to query if a given
+ * scheme is supported on the device.
+ *
+ * <a name="Callbacks"></a>
+ * <h3>Callbacks</h3>
+ * <p>Applications may want to register for informational events in order
+ * to be informed of some internal state update during playback or streaming.
+ * Registration for these events is done via a call to
+ * {@link #setOnEventListener(OnInfoListener)}setOnInfoListener,
+ * In order to receive the respective callback
+ * associated with this listener, applications are required to create
+ * MediaDrm objects on a thread with its own Looper running (main UI
+ * thread by default has a Looper running).
+ *
+ * @hide -- don't expose yet
+ */
+public final class MediaDrm {
+
+ private final static String TAG = "MediaDrm";
+
+ private EventHandler mEventHandler;
+ private OnEventListener mOnEventListener;
+
+ private int mNativeContext;
+
+ /**
+ * Query if the given scheme identified by its UUID is supported on
+ * this device.
+ * @param uuid The UUID of the crypto scheme.
+ */
+ public static final boolean isCryptoSchemeSupported(UUID uuid) {
+ return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid));
+ }
+
+ private static final byte[] getByteArrayFromUUID(UUID uuid) {
+ long msb = uuid.getMostSignificantBits();
+ long lsb = uuid.getLeastSignificantBits();
+
+ byte[] uuidBytes = new byte[16];
+ for (int i = 0; i < 8; ++i) {
+ uuidBytes[i] = (byte)(msb >>> (8 * (7 - i)));
+ uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i)));
+ }
+
+ return uuidBytes;
+ }
+
+ private static final native boolean isCryptoSchemeSupportedNative(byte[] uuid);
+
+ /**
+ * Instantiate a MediaDrm object using opaque, crypto scheme specific
+ * data.
+ * @param uuid The UUID of the crypto scheme.
+ */
+ public MediaDrm(UUID uuid) throws MediaDrmException {
+ Looper looper;
+ if ((looper = Looper.myLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else if ((looper = Looper.getMainLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else {
+ mEventHandler = null;
+ }
+
+ /* Native setup requires a weak reference to our object.
+ * It's easier to create it here than in C++.
+ */
+ native_setup(new WeakReference<MediaDrm>(this),
+ getByteArrayFromUUID(uuid));
+ }
+
+ /**
+ * Register a callback to be invoked when an event occurs
+ *
+ * @param listener the callback that will be run
+ */
+ public void setOnEventListener(OnEventListener listener)
+ {
+ mOnEventListener = listener;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a drm event
+ * occurs.
+ */
+ public interface OnEventListener
+ {
+ /**
+ * Called when an event occurs that requires the app to be notified
+ *
+ * @param md the MediaDrm object on which the event occurred
+ * @param sessionId the DRM session ID on which the event occurred
+ * @param event indicates the event type
+ * @param extra an secondary error code
+ * @param data optional byte array of data that may be associated with the event
+ */
+ void onEvent(MediaDrm md, byte[] sessionId, int event, int extra, byte[] data);
+ }
+
+ /* Do not change these values without updating their counterparts
+ * in include/media/mediadrm.h!
+ */
+ private static final int DRM_EVENT = 200;
+
+ private class EventHandler extends Handler
+ {
+ private MediaDrm mMediaDrm;
+
+ public EventHandler(MediaDrm md, Looper looper) {
+ super(looper);
+ mMediaDrm = md;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mMediaDrm.mNativeContext == 0) {
+ Log.w(TAG, "MediaDrm went away with unhandled events");
+ return;
+ }
+ switch(msg.what) {
+
+ case DRM_EVENT:
+ Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")");
+
+ if (mOnEventListener != null) {
+ Bundle bundle = msg.getData();
+ byte[] sessionId = bundle.getByteArray("sessionId");
+ byte[] data = bundle.getByteArray("data");
+ mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data);
+ }
+ return;
+
+ default:
+ Log.e(TAG, "Unknown message type " + msg.what);
+ return;
+ }
+ }
+ }
+
+ /*
+ * Called from native code when an interesting event happens. This method
+ * just uses the EventHandler system to post the event back to the main app thread.
+ * We use a weak reference to the original MediaPlayer object so that the native
+ * code is safe from the object disappearing from underneath it. (This is
+ * the cookie passed to native_setup().)
+ */
+ private static void postEventFromNative(Object mediadrm_ref,
+ int what, int arg1, int arg2, Object obj)
+ {
+ MediaDrm md = (MediaDrm)((WeakReference)mediadrm_ref).get();
+ if (md == null) {
+ return;
+ }
+ if (md.mEventHandler != null) {
+ Message m = md.mEventHandler.obtainMessage(what, arg1, arg2, obj);
+ md.mEventHandler.sendMessage(m);
+ }
+ }
+
+ /**
+ * Open a new session with the MediaDrm object. A session ID is returned.
+ */
+ public native byte[] openSession() throws MediaDrmException;
+
+ /**
+ * Close a session on the MediaDrm object.
+ */
+ public native void closeSession(byte[] sessionId) throws MediaDrmException;
+
+ public static final int MEDIA_DRM_LICENSE_TYPE_STREAMING = 1;
+ public static final int MEDIA_DRM_LICENSE_TYPE_OFFLINE = 2;
+
+ public final class LicenseRequest {
+ public LicenseRequest() {}
+ public byte[] data;
+ public String defaultUrl;
+ };
+
+ /**
+ * A license request/response exchange occurs between the app and a License
+ * Server to obtain the keys required to decrypt the content. getLicenseRequest()
+ * is used to obtain an opaque license request byte array that is delivered to the
+ * license server. The opaque license request byte array is returned in
+ * LicenseReqeust.data. The recommended URL to deliver the license request to is
+ * returned in LicenseRequest.defaultUrl
+ *
+ * @param sessonId the session ID for the drm session
+ * @param init container-specific data, its meaning is interpreted based on the
+ * mime type provided in the mimeType parameter. It could contain, for example,
+ * the content ID, key ID or other data obtained from the content metadata that is
+ * required in generating the license request.
+ * @param mimeType identifies the mime type of the content
+ * @param licenseType specifes if the license is for streaming or offline content
+ * @param optionalParameters are included in the license server request message to
+ * allow a client application to provide additional message parameters to the server.
+ */
+ public native LicenseRequest getLicenseRequest( byte[] sessionId, byte[] init,
+ String mimeType, int licenseType,
+ HashMap<String, String> optionalParameters )
+ throws MediaDrmException;
+
+ /**
+ * After a license response is received by the app, it is provided to the DRM plugin
+ * using provideLicenseResponse.
+ *
+ * @param sessionId the session ID for the DRM session
+ * @param response the byte array response from the server
+ */
+ public native void provideLicenseResponse( byte[] sessionId, byte[] response )
+ throws MediaDrmException;
+
+ /**
+ * Remove the keys associated with a license for a session
+ * @param sessionId the session ID for the DRM session
+ */
+ public native void removeLicense( byte[] sessionId ) throws MediaDrmException;
+
+ /**
+ * Request an informative description of the license for the session. The status is
+ * in the form of {name, value} pairs. Since DRM license policies vary by vendor,
+ * the specific status field names are determined by each DRM vendor. Refer to your
+ * DRM provider documentation for definitions of the field names for a particular
+ * DrmEngine.
+ *
+ * @param sessionId the session ID for the DRM session
+ */
+ public native HashMap<String, String> queryLicenseStatus( byte[] sessionId )
+ throws MediaDrmException;
+
+ public final class ProvisionRequest {
+ public ProvisionRequest() {}
+ public byte[] data;
+ public String defaultUrl;
+ }
+
+ /**
+ * A provision request/response exchange occurs between the app and a provisioning
+ * server to retrieve a device certificate. getProvisionRequest is used to obtain
+ * an opaque license request byte array that is delivered to the provisioning server.
+ * The opaque provision request byte array is returned in ProvisionRequest.data
+ * The recommended URL to deliver the license request to is returned in
+ * ProvisionRequest.defaultUrl.
+ */
+ public native ProvisionRequest getProvisionRequest() throws MediaDrmException;
+
+ /**
+ * After a provision response is received by the app, it is provided to the DRM
+ * plugin using this method.
+ *
+ * @param response the opaque provisioning response byte array to provide to the
+ * DrmEngine.
+ */
+ public native void provideProvisionResponse( byte[] response )
+ throws MediaDrmException;
+
+ /**
+ * A means of enforcing the contractual requirement for a concurrent stream limit
+ * per subscriber across devices is provided via SecureStop. SecureStop is a means
+ * of securely monitoring the lifetime of sessions. Since playback on a device can
+ * be interrupted due to reboot, power failure, etc. a means of persisting the
+ * lifetime information on the device is needed.
+ *
+ * A signed version of the sessionID is written to persistent storage on the device
+ * when each MediaCrypto object is created. The sessionID is signed by the device
+ * private key to prevent tampering.
+ *
+ * In the normal case, playback will be completed, the session destroyed and the
+ * Secure Stops will be queried. The App queries secure stops and forwards the
+ * secure stop message to the server which verifies the signature and notifies the
+ * server side database that the session destruction has been confirmed. The persisted
+ * record on the client is only removed after positive confirmation that the server
+ * received the message using releaseSecureStops().
+ */
+ public native List<byte[]> getSecureStops() throws MediaDrmException;
+
+
+ /**
+ * Process the SecureStop server response message ssRelease. After authenticating
+ * the message, remove the SecureStops identiied in the response.
+ *
+ * @param ssRelease the server response indicating which secure stops to release
+ */
+ public native void releaseSecureStops( byte[] ssRelease )
+ throws MediaDrmException;
+
+
+ /**
+ * Read a Drm plugin property value, given the property name string. There are several
+ * forms of property access functions, depending on the data type returned.
+ *
+ * Standard fields names are:
+ * vendor String - identifies the maker of the plugin
+ * version String - identifies the version of the plugin
+ * description String - describes the plugin
+ * deviceUniqueId byte[] - The device unique identifier is established during device
+ * provisioning and provides a means of uniquely identifying
+ * each device
+ */
+ public native String getPropertyString( String propertyName )
+ throws MediaDrmException;
+
+ public native byte[] getPropertyByteArray( String propertyName )
+ throws MediaDrmException;
+
+ /**
+ * Write a Drm plugin property value. There are several forms of property setting
+ * functions, depending on the data type being set.
+ */
+ public native void setPropertyString( String propertyName, String value )
+ throws MediaDrmException;
+
+ public native void setPropertyByteArray( String propertyName, byte[] value )
+ throws MediaDrmException;
+
+ @Override
+ protected void finalize() {
+ native_finalize();
+ }
+
+ public native final void release();
+ private static native final void native_init();
+
+ private native final void native_setup(Object mediadrm_this, byte[] uuid)
+ throws MediaDrmException;
+
+ private native final void native_finalize();
+
+ static {
+ System.loadLibrary("media_jni");
+ native_init();
+ }
+}
diff --git a/media/java/android/media/MediaDrmException.java b/media/java/android/media/MediaDrmException.java
new file mode 100644
index 0000000..6f81f90
--- /dev/null
+++ b/media/java/android/media/MediaDrmException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * Exception thrown if MediaDrm object could not be instantiated for
+ * whatever reason.
+ *
+ * @hide -- don't expose yet
+ */
+public final class MediaDrmException extends Exception {
+ public MediaDrmException(String detailMessage) {
+ super(detailMessage);
+ }
+}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index ac8fb74..6873060 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -5,6 +5,7 @@
android_media_MediaCrypto.cpp \
android_media_MediaCodec.cpp \
android_media_MediaCodecList.cpp \
+ android_media_MediaDrm.cpp \
android_media_MediaExtractor.cpp \
android_media_MediaMuxer.cpp \
android_media_MediaPlayer.cpp \
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
new file mode 100644
index 0000000..9938f76
--- /dev/null
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -0,0 +1,817 @@
+/*
+ * Copyright 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaDrm-JNI"
+#include <utils/Log.h>
+
+#include "android_media_MediaDrm.h"
+
+#include "android_runtime/AndroidRuntime.h"
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include <binder/IServiceManager.h>
+#include <media/IDrm.h>
+#include <media/IMediaPlayerService.h>
+#include <media/stagefright/foundation/ADebug.h>
+
+namespace android {
+
+#define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+#define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \
+ var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find method " fieldName);
+
+struct RequestFields {
+ jfieldID data;
+ jfieldID defaultUrl;
+};
+
+struct ArrayListFields {
+ jmethodID init;
+ jmethodID add;
+};
+
+struct HashmapFields {
+ jmethodID init;
+ jmethodID get;
+ jmethodID put;
+ jmethodID entrySet;
+};
+
+struct SetFields {
+ jmethodID iterator;
+};
+
+struct IteratorFields {
+ jmethodID next;
+ jmethodID hasNext;
+};
+
+struct EntryFields {
+ jmethodID getKey;
+ jmethodID getValue;
+};
+
+struct fields_t {
+ jfieldID context;
+ RequestFields licenseRequest;
+ RequestFields provisionRequest;
+ ArrayListFields arraylist;
+ HashmapFields hashmap;
+ SetFields set;
+ IteratorFields iterator;
+ EntryFields entry;
+};
+
+static fields_t gFields;
+
+static bool throwExceptionAsNecessary(
+ JNIEnv *env, status_t err, const char *msg = NULL) {
+
+ if (err == BAD_VALUE) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", msg);
+ return true;
+ } else if (err != OK) {
+ jniThrowException(env, "java/lang/IllegalStateException", msg);
+ return true;
+ }
+ return false;
+}
+
+static sp<IDrm> GetDrm(JNIEnv *env, jobject thiz) {
+ JDrm *jdrm = (JDrm *)env->GetIntField(thiz, gFields.context);
+ return jdrm ? jdrm->getDrm() : NULL;
+}
+
+JDrm::JDrm(
+ JNIEnv *env, jobject thiz, const uint8_t uuid[16]) {
+ mObject = env->NewWeakGlobalRef(thiz);
+ mDrm = MakeDrm(uuid);
+}
+
+JDrm::~JDrm() {
+ mDrm.clear();
+
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ env->DeleteWeakGlobalRef(mObject);
+ mObject = NULL;
+}
+
+// static
+sp<IDrm> JDrm::MakeDrm() {
+ sp<IServiceManager> sm = defaultServiceManager();
+
+ sp<IBinder> binder =
+ sm->getService(String16("media.player"));
+
+ sp<IMediaPlayerService> service =
+ interface_cast<IMediaPlayerService>(binder);
+
+ if (service == NULL) {
+ return NULL;
+ }
+
+ sp<IDrm> drm = service->makeDrm();
+
+ if (drm == NULL || (drm->initCheck() != OK && drm->initCheck() != NO_INIT)) {
+ return NULL;
+ }
+
+ return drm;
+}
+
+// static
+sp<IDrm> JDrm::MakeDrm(const uint8_t uuid[16]) {
+ sp<IDrm> drm = MakeDrm();
+
+ if (drm == NULL) {
+ return NULL;
+ }
+
+ status_t err = drm->createPlugin(uuid);
+
+ if (err != OK) {
+ return NULL;
+ }
+
+ return drm;
+}
+
+// static
+bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16]) {
+ sp<IDrm> drm = MakeDrm();
+
+ if (drm == NULL) {
+ return false;
+ }
+
+ return drm->isCryptoSchemeSupported(uuid);
+}
+
+status_t JDrm::initCheck() const {
+ return mDrm == NULL ? NO_INIT : OK;
+}
+
+// JNI conversion utilities
+static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray) {
+ Vector<uint8_t> vector;
+ size_t length = env->GetArrayLength(byteArray);
+ vector.insertAt((size_t)0, length);
+ env->GetByteArrayRegion(byteArray, 0, length, (jbyte *)vector.editArray());
+ return vector;
+}
+
+static jbyteArray VectorToJByteArray(JNIEnv *env, Vector<uint8_t> const &vector) {
+ size_t length = vector.size();
+ jbyteArray result = env->NewByteArray(length);
+ if (result != NULL) {
+ env->SetByteArrayRegion(result, 0, length, (jbyte *)vector.array());
+ }
+ return result;
+}
+
+static String8 JStringToString8(JNIEnv *env, jstring const &jstr) {
+ jboolean isCopy;
+ String8 result;
+
+ const char *s = env->GetStringUTFChars(jstr, &isCopy);
+ if (s) {
+ result = s;
+ env->ReleaseStringUTFChars(jstr, s);
+ }
+ return result;
+}
+/*
+ import java.util.HashMap;
+ import java.util.Set;
+ import java.Map.Entry;
+ import jav.util.Iterator;
+
+ HashMap<k, v> hm;
+ Set<Entry<k, v> > s = hm.entrySet();
+ Iterator i = s.iterator();
+ Entry e = s.next();
+*/
+
+static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env, jobject &hashMap) {
+ jclass clazz;
+ FIND_CLASS(clazz, "java/lang/String");
+ KeyedVector<String8, String8> keyedVector;
+
+ jobject entrySet = env->CallObjectMethod(hashMap, gFields.hashmap.entrySet);
+ if (entrySet) {
+ jobject iterator = env->CallObjectMethod(entrySet, gFields.set.iterator);
+ if (iterator) {
+ jboolean hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext);
+ while (hasNext) {
+ jobject entry = env->CallObjectMethod(iterator, gFields.iterator.next);
+ if (entry) {
+ jobject obj = env->CallObjectMethod(entry, gFields.entry.getKey);
+ if (!env->IsInstanceOf(obj, clazz)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ }
+ jstring jkey = static_cast<jstring>(obj);
+
+ obj = env->CallObjectMethod(entry, gFields.entry.getValue);
+ if (!env->IsInstanceOf(obj, clazz)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ }
+ jstring jvalue = static_cast<jstring>(obj);
+
+ String8 key = JStringToString8(env, jkey);
+ String8 value = JStringToString8(env, jvalue);
+ keyedVector.add(key, value);
+
+ env->DeleteLocalRef(jkey);
+ env->DeleteLocalRef(jvalue);
+ hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext);
+ }
+ env->DeleteLocalRef(entry);
+ }
+ env->DeleteLocalRef(iterator);
+ }
+ env->DeleteLocalRef(entrySet);
+ }
+ return keyedVector;
+}
+
+static jobject KeyedVectorToHashMap (JNIEnv *env, KeyedVector<String8, String8> const &map) {
+ jclass clazz;
+ FIND_CLASS(clazz, "java/util/HashMap");
+ jobject hashMap = env->NewObject(clazz, gFields.hashmap.init);
+ for (size_t i = 0; i < map.size(); ++i) {
+ jstring jkey = env->NewStringUTF(map.keyAt(i).string());
+ jstring jvalue = env->NewStringUTF(map.valueAt(i).string());
+ env->CallObjectMethod(hashMap, gFields.hashmap.put, jkey, jvalue);
+ env->DeleteLocalRef(jkey);
+ env->DeleteLocalRef(jvalue);
+ }
+ return hashMap;
+}
+
+static jobject ListOfVectorsToArrayListOfByteArray(JNIEnv *env,
+ List<Vector<uint8_t> > list) {
+ jclass clazz;
+ FIND_CLASS(clazz, "java/util/ArrayList");
+ jobject arrayList = env->NewObject(clazz, gFields.arraylist.init);
+ List<Vector<uint8_t> >::iterator iter = list.begin();
+ while (iter != list.end()) {
+ jbyteArray byteArray = VectorToJByteArray(env, *iter);
+ env->CallBooleanMethod(arrayList, gFields.arraylist.add, byteArray);
+ env->DeleteLocalRef(byteArray);
+ iter++;
+ }
+
+ return arrayList;
+}
+
+} // namespace android
+
+using namespace android;
+
+static sp<JDrm> setDrm(
+ JNIEnv *env, jobject thiz, const sp<JDrm> &drm) {
+ sp<JDrm> old = (JDrm *)env->GetIntField(thiz, gFields.context);
+ if (drm != NULL) {
+ drm->incStrong(thiz);
+ }
+ if (old != NULL) {
+ old->decStrong(thiz);
+ }
+ env->SetIntField(thiz, gFields.context, (int)drm.get());
+
+ return old;
+}
+
+static bool CheckSession(JNIEnv *env, const sp<IDrm> &drm, jbyteArray const &jsessionId)
+{
+ if (drm == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return false;
+ }
+
+ if (jsessionId == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return false;
+ }
+ return true;
+}
+
+static void android_media_MediaDrm_release(JNIEnv *env, jobject thiz) {
+ setDrm(env, thiz, NULL);
+}
+
+static void android_media_MediaDrm_native_init(JNIEnv *env) {
+ jclass clazz;
+ FIND_CLASS(clazz, "android/media/MediaDrm");
+ GET_FIELD_ID(gFields.context, clazz, "mNativeContext", "I");
+
+ FIND_CLASS(clazz, "android/media/MediaDrm$LicenseRequest");
+ GET_FIELD_ID(gFields.licenseRequest.data, clazz, "data", "[B");
+ GET_FIELD_ID(gFields.licenseRequest.defaultUrl, clazz, "defaultUrl", "Ljava/lang/String;");
+
+ FIND_CLASS(clazz, "android/media/MediaDrm$ProvisionRequest");
+ GET_FIELD_ID(gFields.provisionRequest.data, clazz, "data", "[B");
+ GET_FIELD_ID(gFields.provisionRequest.defaultUrl, clazz, "defaultUrl", "Ljava/lang/String;");
+
+ FIND_CLASS(clazz, "java/util/ArrayList");
+ GET_METHOD_ID(gFields.arraylist.init, clazz, "<init>", "()V");
+ GET_METHOD_ID(gFields.arraylist.add, clazz, "add", "(Ljava/lang/Object;)Z");
+
+ FIND_CLASS(clazz, "java/util/HashMap");
+ GET_METHOD_ID(gFields.hashmap.init, clazz, "<init>", "()V");
+ GET_METHOD_ID(gFields.hashmap.get, clazz, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
+ GET_METHOD_ID(gFields.hashmap.put, clazz, "put",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+ GET_METHOD_ID(gFields.hashmap.entrySet, clazz, "entrySet", "()Ljava/util/Set;");
+
+ FIND_CLASS(clazz, "java/util/Set");
+ GET_METHOD_ID(gFields.set.iterator, clazz, "iterator", "()Ljava/util/Iterator;");
+
+ FIND_CLASS(clazz, "java/util/Iterator");
+ GET_METHOD_ID(gFields.iterator.next, clazz, "next", "()Ljava/lang/Object;");
+ GET_METHOD_ID(gFields.iterator.hasNext, clazz, "hasNext", "()Z");
+
+ FIND_CLASS(clazz, "java/util/Map$Entry");
+ GET_METHOD_ID(gFields.entry.getKey, clazz, "getKey", "()Ljava/lang/Object;");
+ GET_METHOD_ID(gFields.entry.getValue, clazz, "getValue", "()Ljava/lang/Object;");
+}
+
+static void android_media_MediaDrm_native_setup(
+ JNIEnv *env, jobject thiz,
+ jobject weak_this, jbyteArray uuidObj) {
+
+ if (uuidObj == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ Vector<uint8_t> uuid = JByteArrayToVector(env, uuidObj);
+
+ if (uuid.size() != 16) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ sp<JDrm> drm = new JDrm(env, thiz, uuid.array());
+
+ status_t err = drm->initCheck();
+
+ if (err != OK) {
+ jniThrowException(
+ env,
+ "android/media/MediaDrmException",
+ "Failed to instantiate drm object.");
+ return;
+ }
+
+ setDrm(env, thiz, drm);
+}
+
+static void android_media_MediaDrm_native_finalize(
+ JNIEnv *env, jobject thiz) {
+ android_media_MediaDrm_release(env, thiz);
+}
+
+static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative(
+ JNIEnv *env, jobject thiz, jbyteArray uuidObj) {
+
+ if (uuidObj == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return false;
+ }
+
+ Vector<uint8_t> uuid = JByteArrayToVector(env, uuidObj);
+
+ if (uuid.size() != 16) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalArgumentException",
+ NULL);
+ return false;
+ }
+
+ return JDrm::IsCryptoSchemeSupported(uuid.array());
+}
+
+static jbyteArray android_media_MediaDrm_openSession(
+ JNIEnv *env, jobject thiz) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (drm == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ Vector<uint8_t> sessionId;
+ status_t err = drm->openSession(sessionId);
+
+ if (throwExceptionAsNecessary(env, err, "Failed to open session")) {
+ return NULL;
+ }
+
+ return VectorToJByteArray(env, sessionId);
+}
+
+static void android_media_MediaDrm_closeSession(
+ JNIEnv *env, jobject thiz, jbyteArray jsessionId) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (!CheckSession(env, drm, jsessionId)) {
+ return;
+ }
+
+ Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
+
+ status_t err = drm->closeSession(sessionId);
+
+ throwExceptionAsNecessary(env, err, "Failed to close session");
+}
+
+static jobject android_media_MediaDrm_getLicenseRequest(
+ JNIEnv *env, jobject thiz, jbyteArray jsessionId, jbyteArray jinitData,
+ jstring jmimeType, jint jlicenseType, jobject joptParams) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (!CheckSession(env, drm, jsessionId)) {
+ return NULL;
+ }
+
+ Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
+
+ Vector<uint8_t> initData;
+ if (jinitData != NULL) {
+ initData = JByteArrayToVector(env, jinitData);
+ }
+
+ String8 mimeType;
+ if (jmimeType != NULL) {
+ mimeType = JStringToString8(env, jmimeType);
+ }
+
+ DrmPlugin::LicenseType licenseType = (DrmPlugin::LicenseType)jlicenseType;
+
+ KeyedVector<String8, String8> optParams;
+ if (joptParams != NULL) {
+ optParams = HashMapToKeyedVector(env, joptParams);
+ }
+
+ Vector<uint8_t> request;
+ String8 defaultUrl;
+
+ status_t err = drm->getLicenseRequest(sessionId, initData, mimeType,
+ licenseType, optParams, request, defaultUrl);
+
+ if (throwExceptionAsNecessary(env, err, "Failed to get license request")) {
+ return NULL;
+ }
+
+ // Fill out return obj
+ jclass clazz;
+ FIND_CLASS(clazz, "android/media/MediaDrm$LicenseRequest");
+
+ jobject licenseObj = NULL;
+
+ if (clazz) {
+ licenseObj = env->AllocObject(clazz);
+ jbyteArray jrequest = VectorToJByteArray(env, request);
+ env->SetObjectField(licenseObj, gFields.licenseRequest.data, jrequest);
+
+ jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string());
+ env->SetObjectField(licenseObj, gFields.licenseRequest.defaultUrl, jdefaultUrl);
+ }
+
+ return licenseObj;
+}
+
+static void android_media_MediaDrm_provideLicenseResponse(
+ JNIEnv *env, jobject thiz, jbyteArray jsessionId, jbyteArray jresponse) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (!CheckSession(env, drm, jsessionId)) {
+ return;
+ }
+
+ Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
+
+ if (jresponse == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+ Vector<uint8_t> response(JByteArrayToVector(env, jresponse));
+
+ status_t err = drm->provideLicenseResponse(sessionId, response);
+
+ throwExceptionAsNecessary(env, err, "Failed to handle license response");
+}
+
+static void android_media_MediaDrm_removeLicense(
+ JNIEnv *env, jobject thiz, jbyteArray jsessionId) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (!CheckSession(env, drm, jsessionId)) {
+ return;
+ }
+
+ Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
+
+ status_t err = drm->removeLicense(sessionId);
+
+ throwExceptionAsNecessary(env, err, "Failed to remove license");
+}
+
+static jobject android_media_MediaDrm_queryLicenseStatus(
+ JNIEnv *env, jobject thiz, jbyteArray jsessionId) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (!CheckSession(env, drm, jsessionId)) {
+ return NULL;
+ }
+ Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
+
+ KeyedVector<String8, String8> infoMap;
+
+ status_t err = drm->queryLicenseStatus(sessionId, infoMap);
+
+ if (throwExceptionAsNecessary(env, err, "Failed to query license")) {
+ return NULL;
+ }
+
+ return KeyedVectorToHashMap(env, infoMap);
+}
+
+static jobject android_media_MediaDrm_getProvisionRequest(
+ JNIEnv *env, jobject thiz) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (drm == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ Vector<uint8_t> request;
+ String8 defaultUrl;
+
+ status_t err = drm->getProvisionRequest(request, defaultUrl);
+
+ if (throwExceptionAsNecessary(env, err, "Failed to get provision request")) {
+ return NULL;
+ }
+
+ // Fill out return obj
+ jclass clazz;
+ FIND_CLASS(clazz, "android/media/MediaDrm$ProvisionRequest");
+
+ jobject provisionObj = NULL;
+
+ if (clazz) {
+ provisionObj = env->AllocObject(clazz);
+ jbyteArray jrequest = VectorToJByteArray(env, request);
+ env->SetObjectField(provisionObj, gFields.provisionRequest.data, jrequest);
+
+ jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string());
+ env->SetObjectField(provisionObj, gFields.provisionRequest.defaultUrl, jdefaultUrl);
+ }
+
+ return provisionObj;
+}
+
+static void android_media_MediaDrm_provideProvisionResponse(
+ JNIEnv *env, jobject thiz, jbyteArray jresponse) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (drm == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (jresponse == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ Vector<uint8_t> response(JByteArrayToVector(env, jresponse));
+
+ status_t err = drm->provideProvisionResponse(response);
+
+ throwExceptionAsNecessary(env, err, "Failed to handle provision response");
+}
+
+static jobject android_media_MediaDrm_getSecureStops(
+ JNIEnv *env, jobject thiz) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (drm == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ List<Vector<uint8_t> > secureStops;
+
+ status_t err = drm->getSecureStops(secureStops);
+
+ if (throwExceptionAsNecessary(env, err, "Failed to get secure stops")) {
+ return NULL;
+ }
+
+ return ListOfVectorsToArrayListOfByteArray(env, secureStops);
+}
+
+static void android_media_MediaDrm_releaseSecureStops(
+ JNIEnv *env, jobject thiz, jbyteArray jssRelease) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (drm == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ Vector<uint8_t> ssRelease(JByteArrayToVector(env, jssRelease));
+
+ status_t err = drm->releaseSecureStops(ssRelease);
+
+ throwExceptionAsNecessary(env, err, "Failed to release secure stops");
+}
+
+static jstring android_media_MediaDrm_getPropertyString(
+ JNIEnv *env, jobject thiz, jstring jname) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (drm == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ if (jname == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return NULL;
+ }
+
+ String8 name = JStringToString8(env, jname);
+ String8 value;
+
+ status_t err = drm->getPropertyString(name, value);
+
+ if (throwExceptionAsNecessary(env, err, "Failed to get property")) {
+ return NULL;
+ }
+
+ return env->NewStringUTF(value.string());
+}
+
+static jbyteArray android_media_MediaDrm_getPropertyByteArray(
+ JNIEnv *env, jobject thiz, jstring jname) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (drm == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ if (jname == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return NULL;
+ }
+
+ String8 name = JStringToString8(env, jname);
+ Vector<uint8_t> value;
+
+ status_t err = drm->getPropertyByteArray(name, value);
+
+ if (throwExceptionAsNecessary(env, err, "Failed to get property")) {
+ return NULL;
+ }
+
+ return VectorToJByteArray(env, value);
+}
+
+static void android_media_MediaDrm_setPropertyString(
+ JNIEnv *env, jobject thiz, jstring jname, jstring jvalue) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (drm == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (jname == NULL || jvalue == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ String8 name = JStringToString8(env, jname);
+ String8 value = JStringToString8(env, jvalue);
+
+ status_t err = drm->setPropertyString(name, value);
+
+ throwExceptionAsNecessary(env, err, "Failed to set property");
+}
+
+static void android_media_MediaDrm_setPropertyByteArray(
+ JNIEnv *env, jobject thiz, jstring jname, jbyteArray jvalue) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+
+ if (drm == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (jname == NULL || jvalue == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ String8 name = JStringToString8(env, jname);
+ Vector<uint8_t> value = JByteArrayToVector(env, jvalue);
+
+ status_t err = drm->setPropertyByteArray(name, value);
+
+ throwExceptionAsNecessary(env, err, "Failed to set property");
+}
+
+
+static JNINativeMethod gMethods[] = {
+ { "release", "()V", (void *)android_media_MediaDrm_release },
+ { "native_init", "()V", (void *)android_media_MediaDrm_native_init },
+
+ { "native_setup", "(Ljava/lang/Object;[B)V",
+ (void *)android_media_MediaDrm_native_setup },
+
+ { "native_finalize", "()V",
+ (void *)android_media_MediaDrm_native_finalize },
+
+ { "isCryptoSchemeSupportedNative", "([B)Z",
+ (void *)android_media_MediaDrm_isCryptoSchemeSupportedNative },
+
+ { "openSession", "()[B",
+ (void *)android_media_MediaDrm_openSession },
+
+ { "closeSession", "([B)V",
+ (void *)android_media_MediaDrm_closeSession },
+
+ { "getLicenseRequest", "([B[BLjava/lang/String;ILjava/util/HashMap;)"
+ "Landroid/media/MediaDrm$LicenseRequest;",
+ (void *)android_media_MediaDrm_getLicenseRequest },
+
+ { "provideLicenseResponse", "([B[B)V",
+ (void *)android_media_MediaDrm_provideLicenseResponse },
+
+ { "removeLicense", "([B)V",
+ (void *)android_media_MediaDrm_removeLicense },
+
+ { "queryLicenseStatus", "([B)Ljava/util/HashMap;",
+ (void *)android_media_MediaDrm_queryLicenseStatus },
+
+ { "getProvisionRequest", "()Landroid/media/MediaDrm$ProvisionRequest;",
+ (void *)android_media_MediaDrm_getProvisionRequest },
+
+ { "provideProvisionResponse", "([B)V",
+ (void *)android_media_MediaDrm_provideProvisionResponse },
+
+ { "getSecureStops", "()Ljava/util/List;",
+ (void *)android_media_MediaDrm_getSecureStops },
+
+ { "releaseSecureStops", "([B)V",
+ (void *)android_media_MediaDrm_releaseSecureStops },
+
+ { "getPropertyString", "(Ljava/lang/String;)Ljava/lang/String;",
+ (void *)android_media_MediaDrm_getPropertyString },
+
+ { "getPropertyByteArray", "(Ljava/lang/String;)[B",
+ (void *)android_media_MediaDrm_getPropertyByteArray },
+
+ { "setPropertyString", "(Ljava/lang/String;Ljava/lang/String;)V",
+ (void *)android_media_MediaDrm_setPropertyString },
+
+ { "setPropertyByteArray", "(Ljava/lang/String;[B)V",
+ (void *)android_media_MediaDrm_setPropertyByteArray },
+};
+
+int register_android_media_Drm(JNIEnv *env) {
+ return AndroidRuntime::registerNativeMethods(env,
+ "android/media/MediaDrm", gMethods, NELEM(gMethods));
+}
+
diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h
new file mode 100644
index 0000000..01067c4
--- /dev/null
+++ b/media/jni/android_media_MediaDrm.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_MEDIA_DRM_H_
+#define _ANDROID_MEDIA_DRM_H_
+
+#include "jni.h"
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct IDrm;
+
+struct JDrm : public RefBase {
+ static bool IsCryptoSchemeSupported(const uint8_t uuid[16]);
+
+ JDrm(JNIEnv *env, jobject thiz, const uint8_t uuid[16]);
+
+ status_t initCheck() const;
+
+ sp<IDrm> getDrm() { return mDrm; }
+
+protected:
+ virtual ~JDrm();
+
+private:
+ jweak mObject;
+ sp<IDrm> mDrm;
+
+ static sp<IDrm> MakeDrm();
+ static sp<IDrm> MakeDrm(const uint8_t uuid[16]);
+
+ DISALLOW_EVIL_CONSTRUCTORS(JDrm);
+};
+
+} // namespace android
+
+#endif // _ANDROID_MEDIA_DRM_H_
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index e9f6a1e..c5098ce 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -879,6 +879,7 @@
}
extern int register_android_media_Crypto(JNIEnv *env);
+extern int register_android_media_Drm(JNIEnv *env);
extern int register_android_media_MediaCodec(JNIEnv *env);
extern int register_android_media_MediaExtractor(JNIEnv *env);
extern int register_android_media_MediaCodecList(JNIEnv *env);
@@ -979,6 +980,11 @@
goto bail;
}
+ if (register_android_media_Drm(env) < 0) {
+ ALOGE("ERROR: MediaDrm native registration failed");
+ goto bail;
+ }
+
/* success -- return valid version number */
result = JNI_VERSION_1_4;