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;