Implement async event callout from drm plugin to Java app

Change-Id: I451b7c6f766aafac39ce432a71ef5a03bffe29f1
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 3cdf261..4eb0c56 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -25,6 +25,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Bundle;
+import android.os.Parcel;
 import android.util.Log;
 
 /**
@@ -136,10 +137,8 @@
     public static final int MEDIA_DRM_EVENT_KEY_EXPIRED = 3;
     public static final int MEDIA_DRM_EVENT_VENDOR_DEFINED = 4;
 
-    /* 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;
@@ -161,10 +160,18 @@
                 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);
+                    if (msg.obj != null && msg.obj instanceof Parcel) {
+                        Parcel parcel = (Parcel)msg.obj;
+                        byte[] sessionId = parcel.createByteArray();
+                        if (sessionId.length == 0) {
+                            sessionId = null;
+                        }
+                        byte[] data = parcel.createByteArray();
+                        if (data.length == 0) {
+                            data = null;
+                        }
+                        mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data);
+                    }
                 }
                 return;
 
@@ -183,14 +190,14 @@
      * the cookie passed to native_setup().)
      */
     private static void postEventFromNative(Object mediadrm_ref,
-                                            int what, int arg1, int arg2, Object obj)
+                                            int eventType, int extra, 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);
+            Message m = md.mEventHandler.obtainMessage(DRM_EVENT, eventType, extra, obj);
             md.mEventHandler.sendMessage(m);
         }
     }
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 1618edf..c32ba9d 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -21,10 +21,12 @@
 #include "android_media_MediaDrm.h"
 
 #include "android_runtime/AndroidRuntime.h"
+#include "android_os_Parcel.h"
 #include "jni.h"
 #include "JNIHelp.h"
 
 #include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
 #include <media/IDrm.h>
 #include <media/IMediaPlayerService.h>
 #include <media/stagefright/foundation/ADebug.h>
@@ -43,6 +45,15 @@
     var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \
     LOG_FATAL_IF(! var, "Unable to find method " fieldName);
 
+#define GET_STATIC_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+    var = env->GetStaticFieldID(clazz, fieldName, fieldDescriptor); \
+    LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+#define GET_STATIC_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \
+    var = env->GetStaticMethodID(clazz, fieldName, fieldDescriptor); \
+    LOG_FATAL_IF(! var, "Unable to find static method " fieldName);
+
+
 struct RequestFields {
     jfieldID data;
     jfieldID defaultUrl;
@@ -74,8 +85,16 @@
     jmethodID getValue;
 };
 
+struct EventTypes {
+    int kEventProvisionRequired;
+    int kEventKeyRequired;
+    int kEventKeyExpired;
+    int kEventVendorDefined;
+} gEventTypes;
+
 struct fields_t {
     jfieldID context;
+    jmethodID post_event;
     RequestFields keyRequest;
     RequestFields provisionRequest;
     ArrayListFields arraylist;
@@ -87,6 +106,88 @@
 
 static fields_t gFields;
 
+// ----------------------------------------------------------------------------
+// ref-counted object for callbacks
+class JNIDrmListener: public DrmListener
+{
+public:
+    JNIDrmListener(JNIEnv* env, jobject thiz, jobject weak_thiz);
+    ~JNIDrmListener();
+    virtual void notify(DrmPlugin::EventType eventType, int extra, const Parcel *obj = NULL);
+private:
+    JNIDrmListener();
+    jclass      mClass;     // Reference to MediaDrm class
+    jobject     mObject;    // Weak ref to MediaDrm Java object to call on
+};
+
+JNIDrmListener::JNIDrmListener(JNIEnv* env, jobject thiz, jobject weak_thiz)
+{
+    // Hold onto the MediaDrm class for use in calling the static method
+    // that posts events to the application thread.
+    jclass clazz = env->GetObjectClass(thiz);
+    if (clazz == NULL) {
+        ALOGE("Can't find android/media/MediaDrm");
+        jniThrowException(env, "java/lang/Exception", NULL);
+        return;
+    }
+    mClass = (jclass)env->NewGlobalRef(clazz);
+
+    // We use a weak reference so the MediaDrm object can be garbage collected.
+    // The reference is only used as a proxy for callbacks.
+    mObject  = env->NewGlobalRef(weak_thiz);
+}
+
+JNIDrmListener::~JNIDrmListener()
+{
+    // remove global references
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    env->DeleteGlobalRef(mObject);
+    env->DeleteGlobalRef(mClass);
+}
+
+void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra,
+                            const Parcel *obj)
+{
+    jint jeventType;
+
+    // translate DrmPlugin event types into their java equivalents
+    switch(eventType) {
+        case DrmPlugin::kDrmPluginEventProvisionRequired:
+            jeventType = gEventTypes.kEventProvisionRequired;
+            break;
+        case DrmPlugin::kDrmPluginEventKeyNeeded:
+            jeventType = gEventTypes.kEventKeyRequired;
+            break;
+        case DrmPlugin::kDrmPluginEventKeyExpired:
+            jeventType = gEventTypes.kEventKeyExpired;
+            break;
+        case DrmPlugin::kDrmPluginEventVendorDefined:
+            jeventType = gEventTypes.kEventVendorDefined;
+            break;
+        default:
+            ALOGE("Invalid event DrmPlugin::EventType %d, ignored", (int)eventType);
+            return;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    if (obj && obj->dataSize() > 0) {
+        jobject jParcel = createJavaParcelObject(env);
+        if (jParcel != NULL) {
+            Parcel* nativeParcel = parcelForJavaObject(env, jParcel);
+            nativeParcel->setData(obj->data(), obj->dataSize());
+            env->CallStaticVoidMethod(mClass, gFields.post_event, mObject,
+                    jeventType, extra, jParcel);
+        }
+    }
+
+    if (env->ExceptionCheck()) {
+        ALOGW("An exception occurred while notifying an event.");
+        LOGW_EX(env);
+        env->ExceptionClear();
+    }
+}
+
+
 static bool throwExceptionAsNecessary(
         JNIEnv *env, status_t err, const char *msg = NULL) {
 
@@ -109,6 +210,9 @@
         JNIEnv *env, jobject thiz, const uint8_t uuid[16]) {
     mObject = env->NewWeakGlobalRef(thiz);
     mDrm = MakeDrm(uuid);
+    if (mDrm != NULL) {
+        mDrm->setListener(this);
+    }
 }
 
 JDrm::~JDrm() {
@@ -160,6 +264,25 @@
     return drm;
 }
 
+status_t JDrm::setListener(const sp<DrmListener>& listener) {
+    Mutex::Autolock lock(mLock);
+    mListener = listener;
+    return OK;
+}
+
+void JDrm::notify(DrmPlugin::EventType eventType, int extra, const Parcel *obj) {
+    sp<DrmListener> listener;
+    mLock.lock();
+    listener = mListener;
+    mLock.unlock();
+
+    if (listener != NULL) {
+        Mutex::Autolock lock(mNotifyLock);
+        listener->notify(eventType, extra, obj);
+    }
+}
+
+
 // static
 bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16]) {
     sp<IDrm> drm = MakeDrm();
@@ -194,10 +317,9 @@
 }
 
 static String8 JStringToString8(JNIEnv *env, jstring const &jstr) {
-    jboolean isCopy;
     String8 result;
 
-    const char *s = env->GetStringUTFChars(jstr, &isCopy);
+    const char *s = env->GetStringUTFChars(jstr, NULL);
     if (s) {
         result = s;
         env->ReleaseStringUTFChars(jstr, s);
@@ -322,13 +444,28 @@
 }
 
 static void android_media_MediaDrm_release(JNIEnv *env, jobject thiz) {
-    setDrm(env, thiz, NULL);
+    sp<JDrm> drm = setDrm(env, thiz, NULL);
+    if (drm != NULL) {
+        drm->setListener(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");
+    GET_STATIC_METHOD_ID(gFields.post_event, clazz, "postEventFromNative",
+                         "(Ljava/lang/Object;IILjava/lang/Object;)V");
+
+    jfieldID field;
+    GET_STATIC_FIELD_ID(field, clazz, "MEDIA_DRM_EVENT_PROVISION_REQUIRED", "I");
+    gEventTypes.kEventProvisionRequired = env->GetStaticIntField(clazz, field);
+    GET_STATIC_FIELD_ID(field, clazz, "MEDIA_DRM_EVENT_KEY_REQUIRED", "I");
+    gEventTypes.kEventKeyRequired = env->GetStaticIntField(clazz, field);
+    GET_STATIC_FIELD_ID(field, clazz, "MEDIA_DRM_EVENT_KEY_EXPIRED", "I");
+    gEventTypes.kEventKeyExpired = env->GetStaticIntField(clazz, field);
+    GET_STATIC_FIELD_ID(field, clazz, "MEDIA_DRM_EVENT_VENDOR_DEFINED", "I");
+    gEventTypes.kEventVendorDefined = env->GetStaticIntField(clazz, field);
 
     FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest");
     GET_FIELD_ID(gFields.keyRequest.data, clazz, "data", "[B");
@@ -389,6 +526,8 @@
         return;
     }
 
+    sp<JNIDrmListener> listener = new JNIDrmListener(env, thiz, weak_this);
+    drm->setListener(listener);
     setDrm(env, thiz, drm);
 }
 
diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h
index 01067c4..9b3917f 100644
--- a/media/jni/android_media_MediaDrm.h
+++ b/media/jni/android_media_MediaDrm.h
@@ -20,6 +20,8 @@
 #include "jni.h"
 
 #include <media/stagefright/foundation/ABase.h>
+#include <media/IDrm.h>
+#include <media/IDrmClient.h>
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 
@@ -27,15 +29,24 @@
 
 struct IDrm;
 
-struct JDrm : public RefBase {
+class DrmListener: virtual public RefBase
+{
+public:
+    virtual void notify(DrmPlugin::EventType eventType, int extra,
+                        const Parcel *obj) = 0;
+};
+
+struct JDrm : public BnDrmClient {
     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; }
 
+    void notify(DrmPlugin::EventType, int extra, const Parcel *obj);
+    status_t setListener(const sp<DrmListener>& listener);
+
 protected:
     virtual ~JDrm();
 
@@ -43,6 +54,10 @@
     jweak mObject;
     sp<IDrm> mDrm;
 
+    sp<DrmListener> mListener;
+    Mutex mNotifyLock;
+    Mutex mLock;
+
     static sp<IDrm> MakeDrm();
     static sp<IDrm> MakeDrm(const uint8_t uuid[16]);