Add additional error conditions to MediaDrm
New codes are being added to handle resource
contention, lost session state, frame size too
large and insufficient security level for
decryption. Also cleans up inconsistent use of
tamper detected error where invalid state error
should have been used.
bug:111504510
bug:111505796
test: cts and gts media test cases
Change-Id: Ifa652d31ea151fbdc9faa0dd15b19f885d82b916
diff --git a/api/current.txt b/api/current.txt
index 72dd139..a36ffbe 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -24173,8 +24173,11 @@
public static final class MediaCodec.CryptoException extends java.lang.RuntimeException {
ctor public MediaCodec.CryptoException(int, java.lang.String);
method public int getErrorCode();
+ field public static final int ERROR_FRAME_TOO_LARGE = 8; // 0x8
field public static final int ERROR_INSUFFICIENT_OUTPUT_PROTECTION = 4; // 0x4
+ field public static final int ERROR_INSUFFICIENT_SECURITY = 7; // 0x7
field public static final int ERROR_KEY_EXPIRED = 2; // 0x2
+ field public static final int ERROR_LOST_STATE = 9; // 0x9
field public static final int ERROR_NO_KEY = 1; // 0x1
field public static final int ERROR_RESOURCE_BUSY = 3; // 0x3
field public static final int ERROR_SESSION_NOT_OPENED = 5; // 0x5
@@ -24622,6 +24625,7 @@
method public void setOnEventListener(android.media.MediaDrm.OnEventListener);
method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler);
method public void setOnKeyStatusChangeListener(android.media.MediaDrm.OnKeyStatusChangeListener, android.os.Handler);
+ method public void setOnSessionLostStateListener(android.media.MediaDrm.OnSessionLostStateListener, 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 deprecated int EVENT_KEY_EXPIRED = 3; // 0x3
@@ -24740,6 +24744,10 @@
method public abstract void onKeyStatusChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
}
+ public static abstract interface MediaDrm.OnSessionLostStateListener {
+ method public abstract void onSessionLostState(android.media.MediaDrm, byte[]);
+ }
+
public static final class MediaDrm.ProvisionRequest {
method public byte[] getData();
method public java.lang.String getDefaultUrl();
@@ -24748,6 +24756,12 @@
public static abstract class MediaDrm.SecurityLevel implements java.lang.annotation.Annotation {
}
+ public static final class MediaDrm.SessionException extends java.lang.RuntimeException {
+ ctor public MediaDrm.SessionException(int, java.lang.String);
+ method public int getErrorCode();
+ field public static final int ERROR_RESOURCE_CONTENTION = 1; // 0x1
+ }
+
public class MediaDrmException extends java.lang.Exception {
ctor public MediaDrmException(java.lang.String);
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 0375de3..bc9500d 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -2289,6 +2289,30 @@
*/
public static final int ERROR_UNSUPPORTED_OPERATION = 6;
+ /**
+ * This indicates that the security level of the device is not
+ * sufficient to meet the requirements set by the content owner
+ * in the license policy.
+ */
+ public static final int ERROR_INSUFFICIENT_SECURITY = 7;
+
+ /**
+ * This indicates that the video frame being decrypted exceeds
+ * the size of the device's protected output buffers. When
+ * encountering this error the app should try playing content
+ * of a lower resolution.
+ */
+ public static final int ERROR_FRAME_TOO_LARGE = 8;
+
+ /**
+ * This error indicates that session state has been
+ * invalidated. It can occur on devices that are not capable
+ * of retaining crypto session state across device
+ * suspend/resume. The session must be closed and a new
+ * session opened to resume operation.
+ */
+ public static final int ERROR_LOST_STATE = 9;
+
/** @hide */
@IntDef({
ERROR_NO_KEY,
@@ -2296,7 +2320,10 @@
ERROR_RESOURCE_BUSY,
ERROR_INSUFFICIENT_OUTPUT_PROTECTION,
ERROR_SESSION_NOT_OPENED,
- ERROR_UNSUPPORTED_OPERATION
+ ERROR_UNSUPPORTED_OPERATION,
+ ERROR_INSUFFICIENT_SECURITY,
+ ERROR_FRAME_TOO_LARGE,
+ ERROR_LOST_STATE
})
@Retention(RetentionPolicy.SOURCE)
public @interface CryptoErrorCode {}
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index cdbc7b44..75b3915 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -132,11 +132,19 @@
private static final String PERMISSION = android.Manifest.permission.ACCESS_DRM_CERTIFICATES;
private EventHandler mEventHandler;
- private EventHandler mOnKeyStatusChangeEventHandler;
- private EventHandler mOnExpirationUpdateEventHandler;
+ private EventHandler mKeyStatusChangeHandler;
+ private EventHandler mExpirationUpdateHandler;
+ private EventHandler mSessionLostStateHandler;
+
private OnEventListener mOnEventListener;
private OnKeyStatusChangeListener mOnKeyStatusChangeListener;
private OnExpirationUpdateListener mOnExpirationUpdateListener;
+ private OnSessionLostStateListener mOnSessionLostStateListener;
+
+ private final Object mEventLock = new Object();
+ private final Object mKeyStatusChangeLock = new Object();
+ private final Object mExpirationUpdateLock = new Object();
+ private final Object mSessionLostStateLock = new Object();
private long mNativeContext;
@@ -200,6 +208,35 @@
private static final native boolean isCryptoSchemeSupportedNative(
@NonNull byte[] uuid, @Nullable String mimeType);
+ private EventHandler createHandler() {
+ Looper looper;
+ EventHandler handler;
+ if ((looper = Looper.myLooper()) != null) {
+ handler = new EventHandler(this, looper);
+ } else if ((looper = Looper.getMainLooper()) != null) {
+ handler = new EventHandler(this, looper);
+ } else {
+ handler = null;
+ }
+ return handler;
+ }
+
+ private EventHandler updateHandler(Handler handler) {
+ Looper looper;
+ EventHandler newHandler = null;
+ if (handler != null) {
+ looper = handler.getLooper();
+ } else {
+ looper = Looper.myLooper();
+ }
+ if (looper != null) {
+ if (handler == null || handler.getLooper() != looper) {
+ newHandler = new EventHandler(this, looper);
+ }
+ }
+ return newHandler;
+ }
+
/**
* Instantiate a MediaDrm object
*
@@ -209,14 +246,10 @@
* specified scheme UUID
*/
public MediaDrm(@NonNull UUID uuid) throws UnsupportedSchemeException {
- 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;
- }
+ mEventHandler = createHandler();
+ mKeyStatusChangeHandler = createHandler();
+ mExpirationUpdateHandler = createHandler();
+ mSessionLostStateHandler = createHandler();
/* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++.
@@ -272,6 +305,40 @@
}
/**
+ * Thrown when an error occurs in any method that has a session context.
+ */
+ public static final class SessionException extends RuntimeException {
+ public SessionException(int errorCode, @Nullable String detailMessage) {
+ super(detailMessage);
+ mErrorCode = errorCode;
+ }
+
+ /**
+ * This indicates that apps using MediaDrm sessions are
+ * temporarily exceeding the capacity of available crypto
+ * resources. The app should retry the operation later.
+ */
+ public static final int ERROR_RESOURCE_CONTENTION = 1;
+
+ /** @hide */
+ @IntDef({
+ ERROR_RESOURCE_CONTENTION,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SessionErrorCode {}
+
+ /**
+ * Retrieve the error code associated with the SessionException
+ */
+ @SessionErrorCode
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ private final int mErrorCode;
+ }
+
+ /**
* 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.
@@ -282,15 +349,12 @@
*/
public void setOnExpirationUpdateListener(
@Nullable OnExpirationUpdateListener listener, @Nullable 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);
- }
+ synchronized(mExpirationUpdateLock) {
+ if (listener != null) {
+ mExpirationUpdateHandler = updateHandler(handler);
}
+ mOnExpirationUpdateListener = listener;
}
- mOnExpirationUpdateListener = listener;
}
/**
@@ -324,15 +388,12 @@
*/
public void setOnKeyStatusChangeListener(
@Nullable OnKeyStatusChangeListener listener, @Nullable 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);
- }
+ synchronized(mKeyStatusChangeLock) {
+ if (listener != null) {
+ mKeyStatusChangeHandler = updateHandler(handler);
}
+ mOnKeyStatusChangeListener = listener;
}
- mOnKeyStatusChangeListener = listener;
}
/**
@@ -360,6 +421,46 @@
}
/**
+ * Register a callback to be invoked when session state has been
+ * lost. This event can occur on devices that are not capable of
+ * retaining crypto session state across device suspend/resume
+ * cycles. When this event occurs, the session must be closed and
+ * a new session opened to resume operation.
+ *
+ * @param listener the callback that will be run, or {@code null} to unregister the
+ * previously registered callback.
+ * @param handler the handler on which the listener should be invoked, or
+ * {@code null} if the listener should be invoked on the calling thread's looper.
+ */
+ public void setOnSessionLostStateListener(
+ @Nullable OnSessionLostStateListener listener, @Nullable Handler handler) {
+ synchronized(mSessionLostStateLock) {
+ if (listener != null) {
+ mSessionLostStateHandler = updateHandler(handler);
+ }
+ mOnSessionLostStateListener = listener;
+ }
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the
+ * session state has been lost and is now invalid
+ */
+ public interface OnSessionLostStateListener
+ {
+ /**
+ * Called when session state has lost state, to inform the app
+ * about the condition so it can close the session and open a new
+ * one to resume operation.
+ *
+ * @param md the MediaDrm object on which the event occurred
+ * @param sessionId the DRM session ID on which the event occurred
+ */
+ void onSessionLostState(
+ @NonNull MediaDrm md, @NonNull byte[] sessionId);
+ }
+
+ /**
* Defines the status of a key.
* A KeyStatus for each key in a session is provided to the
* {@link OnKeyStatusChangeListener#onKeyStatusChange}
@@ -437,7 +538,9 @@
*/
public void setOnEventListener(@Nullable OnEventListener listener)
{
- mOnEventListener = listener;
+ synchronized(mEventLock) {
+ mOnEventListener = listener;
+ }
}
/**
@@ -513,6 +616,7 @@
private static final int DRM_EVENT = 200;
private static final int EXPIRATION_UPDATE = 201;
private static final int KEY_STATUS_CHANGE = 202;
+ private static final int SESSION_LOST_STATE = 203;
private class EventHandler extends Handler
{
@@ -532,52 +636,72 @@
switch(msg.what) {
case DRM_EVENT:
- if (mOnEventListener != null) {
- 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;
- }
+ synchronized(mEventLock) {
+ if (mOnEventListener != null) {
+ 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;
+ }
- Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")");
- mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data);
+ Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")");
+ mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data);
+ }
}
}
return;
case KEY_STATUS_CHANGE:
- if (mOnKeyStatusChangeListener != 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);
+ synchronized(mKeyStatusChangeLock) {
+ if (mOnKeyStatusChangeListener != 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 key status changed");
- mOnKeyStatusChangeListener.onKeyStatusChange(mMediaDrm, sessionId,
- keyStatusList, hasNewUsableKey);
+ Log.i(TAG, "Drm key status changed");
+ mOnKeyStatusChangeListener.onKeyStatusChange(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();
+ synchronized(mExpirationUpdateLock) {
+ 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);
+ Log.i(TAG, "Drm key expiration update: " + expirationTime);
+ mOnExpirationUpdateListener.onExpirationUpdate(mMediaDrm, sessionId,
+ expirationTime);
+ }
+ }
+ }
+ }
+ return;
+
+ case SESSION_LOST_STATE:
+ synchronized(mSessionLostStateLock) {
+ if (mOnSessionLostStateListener != null) {
+ if (msg.obj != null && msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel)msg.obj;
+ byte[] sessionId = parcel.createByteArray();
+ Log.i(TAG, "Drm session lost state event: ");
+ mOnSessionLostStateListener.onSessionLostState(mMediaDrm,
+ sessionId);
}
}
}
@@ -619,9 +743,42 @@
if (md == null) {
return;
}
- if (md.mEventHandler != null) {
- Message m = md.mEventHandler.obtainMessage(what, eventType, extra, obj);
- md.mEventHandler.sendMessage(m);
+ switch (what) {
+ case DRM_EVENT:
+ synchronized(md.mEventLock) {
+ if (md.mEventHandler != null) {
+ Message m = md.mEventHandler.obtainMessage(what, eventType, extra, obj);
+ md.mEventHandler.sendMessage(m);
+ }
+ }
+ break;
+ case EXPIRATION_UPDATE:
+ synchronized(md.mExpirationUpdateLock) {
+ if (md.mExpirationUpdateHandler != null) {
+ Message m = md.mExpirationUpdateHandler.obtainMessage(what, obj);
+ md.mExpirationUpdateHandler.sendMessage(m);
+ }
+ }
+ break;
+ case KEY_STATUS_CHANGE:
+ synchronized(md.mKeyStatusChangeLock) {
+ if (md.mKeyStatusChangeHandler != null) {
+ Message m = md.mKeyStatusChangeHandler.obtainMessage(what, obj);
+ md.mKeyStatusChangeHandler.sendMessage(m);
+ }
+ }
+ break;
+ case SESSION_LOST_STATE:
+ synchronized(md.mSessionLostStateLock) {
+ if (md.mSessionLostStateHandler != null) {
+ Message m = md.mSessionLostStateHandler.obtainMessage(what, obj);
+ md.mSessionLostStateHandler.sendMessage(m);
+ }
+ }
+ break;
+ default:
+ Log.e(TAG, "Unknown message type " + what);
+ break;
}
}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 2578608..7b07bea3 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -72,7 +72,10 @@
jint cryptoErrorResourceBusy;
jint cryptoErrorInsufficientOutputProtection;
jint cryptoErrorSessionNotOpened;
+ jint cryptoErrorInsufficientSecurity;
jint cryptoErrorUnsupportedOperation;
+ jint cryptoErrorFrameTooLarge;
+ jint cryptoErrorLostState;
} gCryptoErrorCodes;
static struct CodecActionCodes {
@@ -1005,10 +1008,22 @@
err = gCryptoErrorCodes.cryptoErrorSessionNotOpened;
defaultMsg = "Attempted to use a closed session";
break;
+ case ERROR_DRM_INSUFFICIENT_SECURITY:
+ err = gCryptoErrorCodes.cryptoErrorInsufficientSecurity;
+ defaultMsg = "Required security level is not met";
+ break;
case ERROR_DRM_CANNOT_HANDLE:
err = gCryptoErrorCodes.cryptoErrorUnsupportedOperation;
defaultMsg = "Operation not supported in this configuration";
break;
+ case ERROR_DRM_FRAME_TOO_LARGE:
+ err = gCryptoErrorCodes.cryptoErrorFrameTooLarge;
+ defaultMsg = "Decrytped frame exceeds size of output buffer";
+ break;
+ case ERROR_DRM_SESSION_LOST_STATE:
+ err = gCryptoErrorCodes.cryptoErrorLostState;
+ defaultMsg = "Session state was lost, open a new session and retry";
+ break;
default: /* Other negative DRM error codes go out as is. */
break;
}
@@ -1994,11 +2009,26 @@
gCryptoErrorCodes.cryptoErrorSessionNotOpened =
env->GetStaticIntField(clazz.get(), field);
+ field = env->GetStaticFieldID(clazz.get(), "ERROR_INSUFFICIENT_SECURITY", "I");
+ CHECK(field != NULL);
+ gCryptoErrorCodes.cryptoErrorInsufficientSecurity =
+ env->GetStaticIntField(clazz.get(), field);
+
field = env->GetStaticFieldID(clazz.get(), "ERROR_UNSUPPORTED_OPERATION", "I");
CHECK(field != NULL);
gCryptoErrorCodes.cryptoErrorUnsupportedOperation =
env->GetStaticIntField(clazz.get(), field);
+ field = env->GetStaticFieldID(clazz.get(), "ERROR_FRAME_TOO_LARGE", "I");
+ CHECK(field != NULL);
+ gCryptoErrorCodes.cryptoErrorFrameTooLarge =
+ env->GetStaticIntField(clazz.get(), field);
+
+ field = env->GetStaticFieldID(clazz.get(), "ERROR_LOST_STATE", "I");
+ CHECK(field != NULL);
+ gCryptoErrorCodes.cryptoErrorLostState =
+ env->GetStaticIntField(clazz.get(), field);
+
clazz.reset(env->FindClass("android/media/MediaCodec$CodecException"));
CHECK(clazz.get() != NULL);
field = env->GetStaticFieldID(clazz.get(), "ACTION_TRANSIENT", "I");
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index be71dad5..8336459 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -110,6 +110,7 @@
jint kWhatDrmEvent;
jint kWhatExpirationUpdate;
jint kWhatKeyStatusChange;
+ jint kWhatSessionLostState;
} gEventWhat;
struct KeyTypes {
@@ -141,6 +142,16 @@
jclass classId;
};
+struct SessionExceptionFields {
+ jmethodID init;
+ jclass classId;
+ jfieldID errorCode;
+};
+
+struct SessionExceptionErrorCodes {
+ jint kResourceContention;
+} gSessionExceptionErrorCodes;
+
struct HDCPLevels {
jint kHdcpLevelUnknown;
jint kHdcpNone;
@@ -180,6 +191,7 @@
EntryFields entry;
CertificateFields certificate;
StateExceptionFields stateException;
+ SessionExceptionFields sessionException;
jclass certificateClassId;
jclass hashmapClassId;
jclass arraylistClassId;
@@ -310,6 +322,9 @@
case DrmPlugin::kDrmPluginEventKeysChange:
jwhat = gEventWhat.kWhatKeyStatusChange;
break;
+ case DrmPlugin::kDrmPluginEventSessionLostState:
+ jwhat = gEventWhat.kWhatSessionLostState;
+ break;
default:
ALOGE("Invalid event DrmPlugin::EventType %d, ignored", (int)eventType);
return;
@@ -343,6 +358,30 @@
env->Throw(static_cast<jthrowable>(exception));
}
+static void throwSessionException(JNIEnv *env, const char *msg, status_t err) {
+ ALOGE("Session exception: %s (%d)", msg, err);
+
+ jint jErrorCode = 0;
+ switch(err) {
+ case ERROR_DRM_RESOURCE_CONTENTION:
+ jErrorCode = gSessionExceptionErrorCodes.kResourceContention;
+ break;
+ default:
+ break;
+ }
+
+ jobject exception = env->NewObject(gFields.sessionException.classId,
+ gFields.sessionException.init, static_cast<int>(err),
+ env->NewStringUTF(msg));
+
+ env->SetIntField(exception, gFields.sessionException.errorCode, jErrorCode);
+ env->Throw(static_cast<jthrowable>(exception));
+}
+
+static bool isSessionException(status_t err) {
+ return err == ERROR_DRM_RESOURCE_CONTENTION;
+}
+
static bool throwExceptionAsNecessary(
JNIEnv *env, status_t err, const char *msg = NULL) {
@@ -370,7 +409,7 @@
case ERROR_DRM_CANNOT_HANDLE:
drmMessage = "Invalid parameter or data format";
break;
- case ERROR_DRM_TAMPER_DETECTED:
+ case ERROR_DRM_INVALID_STATE:
drmMessage = "Invalid state";
break;
default:
@@ -399,6 +438,9 @@
jniThrowException(env, "android/media/MediaDrmResetException",
"mediaserver died");
return true;
+ } else if (isSessionException(err)) {
+ throwSessionException(env, msg, err);
+ return true;
} else if (err != OK) {
String8 errbuf;
if (drmMessage != NULL) {
@@ -705,6 +747,8 @@
gEventWhat.kWhatExpirationUpdate = env->GetStaticIntField(clazz, field);
GET_STATIC_FIELD_ID(field, clazz, "KEY_STATUS_CHANGE", "I");
gEventWhat.kWhatKeyStatusChange = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "SESSION_LOST_STATE", "I");
+ gEventWhat.kWhatSessionLostState = env->GetStaticIntField(clazz, field);
GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I");
gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field);
@@ -831,6 +875,14 @@
FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(ILjava/lang/String;)V");
gFields.stateException.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
+
+ FIND_CLASS(clazz, "android/media/MediaDrm$SessionException");
+ GET_METHOD_ID(gFields.sessionException.init, clazz, "<init>", "(ILjava/lang/String;)V");
+ gFields.sessionException.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
+ GET_FIELD_ID(gFields.sessionException.errorCode, clazz, "mErrorCode", "I");
+
+ GET_STATIC_FIELD_ID(field, clazz, "ERROR_RESOURCE_CONTENTION", "I");
+ gSessionExceptionErrorCodes.kResourceContention = env->GetStaticIntField(clazz, field);
}
static void android_media_MediaDrm_native_setup(