Add expiration update and keys change events

In support of unprefixed EME

bug: 19771612
bug: 19771431
Change-Id: Iddef695cfa1a56363a4c173249597e415cb93f50
diff --git a/api/current.txt b/api/current.txt
index e2aed94..a5fac83 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -15330,6 +15330,8 @@
     method public void removeKeys(byte[]);
     method public void restoreKeys(byte[], byte[]);
     method public void setOnEventListener(android.media.MediaDrm.OnEventListener);
+    method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler);
+    method public void setOnKeysChangeListener(android.media.MediaDrm.OnKeysChangeListener, android.os.Handler);
     method public void setPropertyByteArray(java.lang.String, byte[]);
     method public void setPropertyString(java.lang.String, java.lang.String);
     field public static final int EVENT_KEY_EXPIRED = 3; // 0x3
@@ -15337,6 +15339,11 @@
     field public static final deprecated int EVENT_PROVISION_REQUIRED = 1; // 0x1
     field public static final int EVENT_SESSION_RECLAIMED = 5; // 0x5
     field public static final int EVENT_VENDOR_DEFINED = 4; // 0x4
+    field public static final int KEY_STATUS_EXPIRED = 1; // 0x1
+    field public static final int KEY_STATUS_INTERNAL_ERROR = 4; // 0x4
+    field public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2; // 0x2
+    field public static final int KEY_STATUS_PENDING = 3; // 0x3
+    field public static final int KEY_STATUS_USABLE = 0; // 0x0
     field public static final int KEY_TYPE_OFFLINE = 2; // 0x2
     field public static final int KEY_TYPE_RELEASE = 3; // 0x3
     field public static final int KEY_TYPE_STREAMING = 1; // 0x1
@@ -15363,6 +15370,11 @@
     method public int getRequestType();
   }
 
+  public static final class MediaDrm.KeyStatus {
+    method public byte[] getKeyId();
+    method public int getStatusCode();
+  }
+
   public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
     method public java.lang.String getDiagnosticInfo();
   }
@@ -15371,6 +15383,14 @@
     method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]);
   }
 
+  public static abstract interface MediaDrm.OnExpirationUpdateListener {
+    method public abstract void onExpirationUpdate(android.media.MediaDrm, byte[], long);
+  }
+
+  public static abstract interface MediaDrm.OnKeysChangeListener {
+    method public abstract void onKeysChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
+  }
+
   public static final class MediaDrm.ProvisionRequest {
     method public byte[] getData();
     method public java.lang.String getDefaultUrl();
diff --git a/api/system-current.txt b/api/system-current.txt
index f0afa3b..4f4762e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -16539,6 +16539,8 @@
     method public void removeKeys(byte[]);
     method public void restoreKeys(byte[], byte[]);
     method public void setOnEventListener(android.media.MediaDrm.OnEventListener);
+    method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler);
+    method public void setOnKeysChangeListener(android.media.MediaDrm.OnKeysChangeListener, android.os.Handler);
     method public void setPropertyByteArray(java.lang.String, byte[]);
     method public void setPropertyString(java.lang.String, java.lang.String);
     method public void unprovisionDevice();
@@ -16547,6 +16549,11 @@
     field public static final deprecated int EVENT_PROVISION_REQUIRED = 1; // 0x1
     field public static final int EVENT_SESSION_RECLAIMED = 5; // 0x5
     field public static final int EVENT_VENDOR_DEFINED = 4; // 0x4
+    field public static final int KEY_STATUS_EXPIRED = 1; // 0x1
+    field public static final int KEY_STATUS_INTERNAL_ERROR = 4; // 0x4
+    field public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2; // 0x2
+    field public static final int KEY_STATUS_PENDING = 3; // 0x3
+    field public static final int KEY_STATUS_USABLE = 0; // 0x0
     field public static final int KEY_TYPE_OFFLINE = 2; // 0x2
     field public static final int KEY_TYPE_RELEASE = 3; // 0x3
     field public static final int KEY_TYPE_STREAMING = 1; // 0x1
@@ -16573,6 +16580,11 @@
     method public int getRequestType();
   }
 
+  public static final class MediaDrm.KeyStatus {
+    method public byte[] getKeyId();
+    method public int getStatusCode();
+  }
+
   public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
     method public java.lang.String getDiagnosticInfo();
   }
@@ -16581,6 +16593,14 @@
     method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]);
   }
 
+  public static abstract interface MediaDrm.OnExpirationUpdateListener {
+    method public abstract void onExpirationUpdate(android.media.MediaDrm, byte[], long);
+  }
+
+  public static abstract interface MediaDrm.OnKeysChangeListener {
+    method public abstract void onKeysChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
+  }
+
   public static final class MediaDrm.ProvisionRequest {
     method public byte[] getData();
     method public java.lang.String getDefaultUrl();
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 069f7ff..fc5fc43 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -17,9 +17,10 @@
 package android.media;
 
 import java.lang.ref.WeakReference;
-import java.util.UUID;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.UUID;
 import android.annotation.SystemApi;
 import android.os.Handler;
 import android.os.Looper;
@@ -98,12 +99,14 @@
  */
 public final class MediaDrm {
 
-    private final static String TAG = "MediaDrm";
+    private static final String TAG = "MediaDrm";
 
     private static final String PERMISSION = android.Manifest.permission.ACCESS_DRM_CERTIFICATES;
 
     private EventHandler mEventHandler;
     private OnEventListener mOnEventListener;
+    private OnKeysChangeListener mOnKeysChangeListener;
+    private OnExpirationUpdateListener mOnExpirationUpdateListener;
 
     private long mNativeContext;
 
@@ -227,6 +230,148 @@
     }
 
     /**
+     * Register a callback to be invoked when a session expiration update
+     * occurs.  The app's OnExpirationUpdateListener will be notified
+     * when the expiration time of the keys in the session have changed.
+     * @param listener the callback that will be run
+     * @param handler the handler on which the listener should be invoked, or
+     *     null if the listener should be invoked on the calling thread's looper.
+     */
+    public void setOnExpirationUpdateListener(OnExpirationUpdateListener listener,
+            Handler handler)
+    {
+        if (listener != null) {
+            Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
+            if (looper != null) {
+                if (mEventHandler == null || mEventHandler.getLooper() != looper) {
+                    mEventHandler = new EventHandler(this, looper);
+                }
+            }
+        }
+        mOnExpirationUpdateListener = listener;
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a drm session
+     * expiration update occurs
+     */
+    public interface OnExpirationUpdateListener
+    {
+        /**
+         * Called when a session expiration update occurs, to inform the app
+         * about the change in expiration time
+         *
+         * @param md the MediaDrm object on which the event occurred
+         * @param sessionId the DRM session ID on which the event occurred
+         * @param expirationTime the new expiration time for the keys in the session.
+         *     The time is in milliseconds, relative to the Unix epoch.
+         */
+        void onExpirationUpdate(MediaDrm md, byte[] sessionId, long expirationTime);
+    }
+
+    /**
+     * Register a callback to be invoked when the state of keys in a session
+     * change, e.g. when a license update occurs or when a license expires.
+     *
+     * @param listener the callback that will be run when key status changes
+     * @param handler the handler on which the listener should be invoked, or
+     *     null if the listener should be invoked on the calling thread's looper.
+     */
+    public void setOnKeysChangeListener(OnKeysChangeListener listener,
+            Handler handler)
+    {
+        if (listener != null) {
+            Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
+            if (looper != null) {
+                if (mEventHandler == null || mEventHandler.getLooper() != looper) {
+                    mEventHandler = new EventHandler(this, looper);
+                }
+            }
+        }
+        mOnKeysChangeListener = listener;
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the keys in a drm
+     * session change states.
+     */
+    public interface OnKeysChangeListener
+    {
+        /**
+         * Called when the keys in a session change status, such as when the license
+         * is renewed or expires.
+         *
+         * @param md the MediaDrm object on which the event occurred
+         * @param sessionId the DRM session ID on which the event occurred
+         * @param keyInformation a list of {@link MediaDrm.KeyStatus}
+         *     instances indicating the status for each key in the session
+         * @param hasNewUsableKey indicates if a key has been added that is usable,
+         *     which may trigger an attempt to resume playback on the media stream
+         *     if it is currently blocked waiting for a key.
+         */
+        void onKeysChange(MediaDrm md, byte[] sessionId, List<KeyStatus> keyInformation,
+                boolean hasNewUsableKey);
+    }
+
+    /**
+     * The key is currently usable to decrypt media data
+     */
+    public static final int KEY_STATUS_USABLE = 0;
+
+    /**
+     * The key is no longer usable to decrypt media data because its
+     * expiration time has passed.
+     */
+    public static final int KEY_STATUS_EXPIRED = 1;
+
+    /**
+     * The key is not currently usable to decrypt media data because its
+     * output requirements cannot currently be met.
+     */
+    public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2;
+
+    /**
+     * The status of the key is not yet known and is being determined.
+     * The status will be updated with the actual status when it has
+     * been determined.
+     */
+    public static final int KEY_STATUS_PENDING = 3;
+
+    /**
+     * The key is not currently usable to decrypt media data because of an
+     * internal error in processing unrelated to input parameters.  This error
+     * is not actionable by an app.
+     */
+    public static final int KEY_STATUS_INTERNAL_ERROR = 4;
+
+
+    /**
+     * Defines the status of a key.
+     * A KeyStatus for each key in a session is provided to the
+     * {@link OnKeysChangeListener#onKeysChange}
+     * listener.
+     */
+    public static final class KeyStatus {
+        private final byte[] mKeyId;
+        private final int mStatusCode;
+
+        KeyStatus(byte[] keyId, int statusCode) {
+            mKeyId = keyId;
+            mStatusCode = statusCode;
+        }
+
+        /**
+         * Returns the status code for the key
+         */
+        public int getStatusCode() { return mStatusCode; }
+
+        /**
+         * Returns the id for the key
+         */
+        public byte[] getKeyId() { return mKeyId; }
+    }
+
+    /**
      * Register a callback to be invoked when an event occurs
      *
      * @param listener the callback that will be run
@@ -289,6 +434,8 @@
     public static final int EVENT_SESSION_RECLAIMED = 5;
 
     private static final int DRM_EVENT = 200;
+    private static final int EXPIRATION_UPDATE = 201;
+    private static final int KEYS_CHANGE = 202;
 
     private class EventHandler extends Handler
     {
@@ -308,8 +455,6 @@
             switch(msg.what) {
 
             case DRM_EVENT:
-                Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")");
-
                 if (mOnEventListener != null) {
                     if (msg.obj != null && msg.obj instanceof Parcel) {
                         Parcel parcel = (Parcel)msg.obj;
@@ -321,11 +466,46 @@
                         if (data.length == 0) {
                             data = null;
                         }
+
+                        Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")");
                         mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data);
                     }
                 }
                 return;
 
+            case KEYS_CHANGE:
+                if (mOnKeysChangeListener != null) {
+                    if (msg.obj != null && msg.obj instanceof Parcel) {
+                        Parcel parcel = (Parcel)msg.obj;
+                        byte[] sessionId = parcel.createByteArray();
+                        if (sessionId.length > 0) {
+                            List<KeyStatus> keyStatusList = keyStatusListFromParcel(parcel);
+                            boolean hasNewUsableKey = (parcel.readInt() != 0);
+
+                            Log.i(TAG, "Drm keys change");
+                            mOnKeysChangeListener.onKeysChange(mMediaDrm, sessionId, keyStatusList,
+                                    hasNewUsableKey);
+                        }
+                    }
+                }
+                return;
+
+            case EXPIRATION_UPDATE:
+                if (mOnExpirationUpdateListener != null) {
+                    if (msg.obj != null && msg.obj instanceof Parcel) {
+                        Parcel parcel = (Parcel)msg.obj;
+                        byte[] sessionId = parcel.createByteArray();
+                        if (sessionId.length > 0) {
+                            long expirationTime = parcel.readLong();
+
+                            Log.i(TAG, "Drm key expiration update: " + expirationTime);
+                            mOnExpirationUpdateListener.onExpirationUpdate(mMediaDrm, sessionId,
+                                    expirationTime);
+                        }
+                    }
+                }
+                return;
+
             default:
                 Log.e(TAG, "Unknown message type " + msg.what);
                 return;
@@ -333,7 +513,21 @@
         }
     }
 
-    /*
+    /**
+     * Parse a list of KeyStatus objects from an event parcel
+     */
+    private List<KeyStatus> keyStatusListFromParcel(Parcel parcel) {
+        int nelems = parcel.readInt();
+        List<KeyStatus> keyStatusList = new ArrayList(nelems);
+        while (nelems-- > 0) {
+            byte[] keyId = parcel.createByteArray();
+            int keyStatusCode = parcel.readInt();
+            keyStatusList.add(new KeyStatus(keyId, keyStatusCode));
+        }
+        return keyStatusList;
+    }
+
+    /**
      * This method is called from native code when an event occurs.  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
@@ -341,14 +535,14 @@
      * the cookie passed to native_setup().)
      */
     private static void postEventFromNative(Object mediadrm_ref,
-            int eventType, int extra, Object obj)
+            int what, int eventType, int extra, Object obj)
     {
-        MediaDrm md = (MediaDrm)((WeakReference)mediadrm_ref).get();
+        MediaDrm md = (MediaDrm)((WeakReference<MediaDrm>)mediadrm_ref).get();
         if (md == null) {
             return;
         }
         if (md.mEventHandler != null) {
-            Message m = md.mEventHandler.obtainMessage(DRM_EVENT, eventType, extra, obj);
+            Message m = md.mEventHandler.obtainMessage(what, eventType, extra, obj);
             md.mEventHandler.sendMessage(m);
         }
     }
@@ -404,7 +598,7 @@
     /**
      * Contains the opaque data an app uses to request keys from a license server
      */
-    public final static class KeyRequest {
+    public static final class KeyRequest {
         private byte[] mData;
         private String mDefaultUrl;
         private int mRequestType;
@@ -521,7 +715,7 @@
      * Contains the opaque data an app uses to request a certificate from a provisioning
      * server
      */
-    public final static class ProvisionRequest {
+    public static final class ProvisionRequest {
         ProvisionRequest() {}
 
         /**
@@ -812,7 +1006,7 @@
      *
      * @hide - not part of the public API at this time
      */
-    public final static class CertificateRequest {
+    public static final class CertificateRequest {
         private byte[] mData;
         private String mDefaultUrl;
 
@@ -860,7 +1054,7 @@
      *
      * @hide - not part of the public API at this time
      */
-    public final static class Certificate {
+    public static final class Certificate {
         Certificate() {}
 
         /**
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 96d7133..f8146a7 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -96,6 +96,12 @@
     jint kEventSessionReclaimed;
 } gEventTypes;
 
+struct EventWhat {
+    jint kWhatDrmEvent;
+    jint kWhatExpirationUpdate;
+    jint kWhatKeysChange;
+} gEventWhat;
+
 struct KeyTypes {
     jint kKeyTypeStreaming;
     jint kKeyTypeOffline;
@@ -186,25 +192,37 @@
 void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra,
                             const Parcel *obj)
 {
-    jint jeventType;
+    jint jwhat;
+    jint jeventType = 0;
 
     // translate DrmPlugin event types into their java equivalents
     switch (eventType) {
         case DrmPlugin::kDrmPluginEventProvisionRequired:
+            jwhat = gEventWhat.kWhatDrmEvent;
             jeventType = gEventTypes.kEventProvisionRequired;
             break;
         case DrmPlugin::kDrmPluginEventKeyNeeded:
+            jwhat = gEventWhat.kWhatDrmEvent;
             jeventType = gEventTypes.kEventKeyRequired;
             break;
         case DrmPlugin::kDrmPluginEventKeyExpired:
+            jwhat = gEventWhat.kWhatDrmEvent;
             jeventType = gEventTypes.kEventKeyExpired;
             break;
         case DrmPlugin::kDrmPluginEventVendorDefined:
+            jwhat = gEventWhat.kWhatDrmEvent;
             jeventType = gEventTypes.kEventVendorDefined;
             break;
         case DrmPlugin::kDrmPluginEventSessionReclaimed:
+            jwhat = gEventWhat.kWhatDrmEvent;
             jeventType = gEventTypes.kEventSessionReclaimed;
             break;
+        case DrmPlugin::kDrmPluginEventExpirationUpdate:
+            jwhat = gEventWhat.kWhatExpirationUpdate;
+            break;
+         case DrmPlugin::kDrmPluginEventKeysChange:
+            jwhat = gEventWhat.kWhatKeysChange;
+            break;
         default:
             ALOGE("Invalid event DrmPlugin::EventType %d, ignored", (int)eventType);
             return;
@@ -217,7 +235,7 @@
             Parcel* nativeParcel = parcelForJavaObject(env, jParcel);
             nativeParcel->setData(obj->data(), obj->dataSize());
             env->CallStaticVoidMethod(mClass, gFields.post_event, mObject,
-                    jeventType, extra, jParcel);
+                    jwhat, jeventType, extra, jParcel);
             env->DeleteLocalRef(jParcel);
         }
     }
@@ -573,7 +591,7 @@
     FIND_CLASS(clazz, "android/media/MediaDrm");
     GET_FIELD_ID(gFields.context, clazz, "mNativeContext", "J");
     GET_STATIC_METHOD_ID(gFields.post_event, clazz, "postEventFromNative",
-                         "(Ljava/lang/Object;IILjava/lang/Object;)V");
+                         "(Ljava/lang/Object;IIILjava/lang/Object;)V");
 
     jfieldID field;
     GET_STATIC_FIELD_ID(field, clazz, "EVENT_PROVISION_REQUIRED", "I");
@@ -587,6 +605,13 @@
     GET_STATIC_FIELD_ID(field, clazz, "EVENT_SESSION_RECLAIMED", "I");
     gEventTypes.kEventSessionReclaimed = env->GetStaticIntField(clazz, field);
 
+    GET_STATIC_FIELD_ID(field, clazz, "DRM_EVENT", "I");
+    gEventWhat.kWhatDrmEvent = env->GetStaticIntField(clazz, field);
+    GET_STATIC_FIELD_ID(field, clazz, "EXPIRATION_UPDATE", "I");
+    gEventWhat.kWhatExpirationUpdate = env->GetStaticIntField(clazz, field);
+    GET_STATIC_FIELD_ID(field, clazz, "KEYS_CHANGE", "I");
+    gEventWhat.kWhatKeysChange = env->GetStaticIntField(clazz, field);
+
     GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I");
     gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field);
     GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_OFFLINE", "I");
@@ -837,7 +862,7 @@
                 env->SetIntField(keyObj, gFields.keyRequest.requestType,
                         gKeyRequestTypes.kKeyRequestTypeRelease);
                 break;
-            case DrmPlugin::kKeyRequestType_Unknown:
+            default:
                 throwStateException(env, "DRM plugin failure: unknown key request type",
                         ERROR_DRM_UNKNOWN);
                 break;