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;