Loading api/current.txt +14 −0 Original line number Diff line number Diff line Loading @@ -24176,8 +24176,11 @@ package android.media { 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 Loading Loading @@ -24625,6 +24628,7 @@ package android.media { 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 Loading Loading @@ -24743,6 +24747,10 @@ package android.media { 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(); Loading @@ -24751,6 +24759,12 @@ package android.media { 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); } media/java/android/media/MediaCodec.java +28 −1 Original line number Diff line number Diff line Loading @@ -2289,6 +2289,30 @@ final public class MediaCodec { */ 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, Loading @@ -2296,7 +2320,10 @@ final public class MediaCodec { 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 {} Loading media/java/android/media/MediaDrm.java +219 −62 Original line number Diff line number Diff line Loading @@ -132,11 +132,19 @@ public final class MediaDrm implements AutoCloseable { 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; Loading Loading @@ -200,6 +208,35 @@ public final class MediaDrm implements AutoCloseable { 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 * Loading @@ -209,14 +246,10 @@ public final class MediaDrm implements AutoCloseable { * 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++. Loading Loading @@ -271,6 +304,40 @@ public final class MediaDrm implements AutoCloseable { } } /** * 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 Loading @@ -282,16 +349,13 @@ public final class MediaDrm implements AutoCloseable { */ public void setOnExpirationUpdateListener( @Nullable OnExpirationUpdateListener listener, @Nullable Handler handler) { synchronized(mExpirationUpdateLock) { 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); } } mExpirationUpdateHandler = updateHandler(handler); } mOnExpirationUpdateListener = listener; } } /** * Interface definition for a callback to be invoked when a drm session Loading Loading @@ -324,16 +388,13 @@ public final class MediaDrm implements AutoCloseable { */ public void setOnKeyStatusChangeListener( @Nullable OnKeyStatusChangeListener listener, @Nullable Handler handler) { synchronized(mKeyStatusChangeLock) { 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); } } mKeyStatusChangeHandler = updateHandler(handler); } mOnKeyStatusChangeListener = listener; } } /** * Interface definition for a callback to be invoked when the keys in a drm Loading @@ -359,6 +420,46 @@ public final class MediaDrm implements AutoCloseable { boolean hasNewUsableKey); } /** * 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 Loading Loading @@ -437,8 +538,10 @@ public final class MediaDrm implements AutoCloseable { */ public void setOnEventListener(@Nullable OnEventListener listener) { synchronized(mEventLock) { mOnEventListener = listener; } } /** * Interface definition for a callback to be invoked when a drm event Loading Loading @@ -513,6 +616,7 @@ public final class MediaDrm implements AutoCloseable { 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 { Loading @@ -532,6 +636,7 @@ public final class MediaDrm implements AutoCloseable { switch(msg.what) { case DRM_EVENT: synchronized(mEventLock) { if (mOnEventListener != null) { if (msg.obj != null && msg.obj instanceof Parcel) { Parcel parcel = (Parcel)msg.obj; Loading @@ -548,9 +653,11 @@ public final class MediaDrm implements AutoCloseable { mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data); } } } return; case KEY_STATUS_CHANGE: synchronized(mKeyStatusChangeLock) { if (mOnKeyStatusChangeListener != null) { if (msg.obj != null && msg.obj instanceof Parcel) { Parcel parcel = (Parcel)msg.obj; Loading @@ -565,9 +672,11 @@ public final class MediaDrm implements AutoCloseable { } } } } return; case EXPIRATION_UPDATE: synchronized(mExpirationUpdateLock) { if (mOnExpirationUpdateListener != null) { if (msg.obj != null && msg.obj instanceof Parcel) { Parcel parcel = (Parcel)msg.obj; Loading @@ -581,6 +690,21 @@ public final class MediaDrm implements AutoCloseable { } } } } 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); } } } return; default: Loading Loading @@ -619,11 +743,44 @@ public final class MediaDrm implements AutoCloseable { if (md == null) { return; } 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; } } /** * Open a new session with the MediaDrm object. A session ID is returned. Loading media/jni/android_media_MediaCodec.cpp +30 −0 Original line number Diff line number Diff line Loading @@ -72,7 +72,10 @@ static struct CryptoErrorCodes { jint cryptoErrorResourceBusy; jint cryptoErrorInsufficientOutputProtection; jint cryptoErrorSessionNotOpened; jint cryptoErrorInsufficientSecurity; jint cryptoErrorUnsupportedOperation; jint cryptoErrorFrameTooLarge; jint cryptoErrorLostState; } gCryptoErrorCodes; static struct CodecActionCodes { Loading Loading @@ -1005,10 +1008,22 @@ static void throwCryptoException(JNIEnv *env, status_t err, const char *msg) { 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; } Loading Loading @@ -1994,11 +2009,26 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) { 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"); Loading media/jni/android_media_MediaDrm.cpp +53 −1 Original line number Diff line number Diff line Loading @@ -110,6 +110,7 @@ struct EventWhat { jint kWhatDrmEvent; jint kWhatExpirationUpdate; jint kWhatKeyStatusChange; jint kWhatSessionLostState; } gEventWhat; struct KeyTypes { Loading Loading @@ -141,6 +142,16 @@ struct StateExceptionFields { jclass classId; }; struct SessionExceptionFields { jmethodID init; jclass classId; jfieldID errorCode; }; struct SessionExceptionErrorCodes { jint kResourceContention; } gSessionExceptionErrorCodes; struct HDCPLevels { jint kHdcpLevelUnknown; jint kHdcpNone; Loading Loading @@ -180,6 +191,7 @@ struct fields_t { EntryFields entry; CertificateFields certificate; StateExceptionFields stateException; SessionExceptionFields sessionException; jclass certificateClassId; jclass hashmapClassId; jclass arraylistClassId; Loading Loading @@ -310,6 +322,9 @@ void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra, 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; Loading Loading @@ -343,6 +358,30 @@ static void throwStateException(JNIEnv *env, const char *msg, status_t err) { 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) { Loading Loading @@ -370,7 +409,7 @@ static bool throwExceptionAsNecessary( 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: Loading Loading @@ -399,6 +438,9 @@ static bool throwExceptionAsNecessary( 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) { Loading Loading @@ -705,6 +747,8 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) { 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); Loading Loading @@ -831,6 +875,14 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) { 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( Loading Loading
api/current.txt +14 −0 Original line number Diff line number Diff line Loading @@ -24176,8 +24176,11 @@ package android.media { 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 Loading Loading @@ -24625,6 +24628,7 @@ package android.media { 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 Loading Loading @@ -24743,6 +24747,10 @@ package android.media { 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(); Loading @@ -24751,6 +24759,12 @@ package android.media { 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); }
media/java/android/media/MediaCodec.java +28 −1 Original line number Diff line number Diff line Loading @@ -2289,6 +2289,30 @@ final public class MediaCodec { */ 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, Loading @@ -2296,7 +2320,10 @@ final public class MediaCodec { 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 {} Loading
media/java/android/media/MediaDrm.java +219 −62 Original line number Diff line number Diff line Loading @@ -132,11 +132,19 @@ public final class MediaDrm implements AutoCloseable { 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; Loading Loading @@ -200,6 +208,35 @@ public final class MediaDrm implements AutoCloseable { 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 * Loading @@ -209,14 +246,10 @@ public final class MediaDrm implements AutoCloseable { * 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++. Loading Loading @@ -271,6 +304,40 @@ public final class MediaDrm implements AutoCloseable { } } /** * 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 Loading @@ -282,16 +349,13 @@ public final class MediaDrm implements AutoCloseable { */ public void setOnExpirationUpdateListener( @Nullable OnExpirationUpdateListener listener, @Nullable Handler handler) { synchronized(mExpirationUpdateLock) { 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); } } mExpirationUpdateHandler = updateHandler(handler); } mOnExpirationUpdateListener = listener; } } /** * Interface definition for a callback to be invoked when a drm session Loading Loading @@ -324,16 +388,13 @@ public final class MediaDrm implements AutoCloseable { */ public void setOnKeyStatusChangeListener( @Nullable OnKeyStatusChangeListener listener, @Nullable Handler handler) { synchronized(mKeyStatusChangeLock) { 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); } } mKeyStatusChangeHandler = updateHandler(handler); } mOnKeyStatusChangeListener = listener; } } /** * Interface definition for a callback to be invoked when the keys in a drm Loading @@ -359,6 +420,46 @@ public final class MediaDrm implements AutoCloseable { boolean hasNewUsableKey); } /** * 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 Loading Loading @@ -437,8 +538,10 @@ public final class MediaDrm implements AutoCloseable { */ public void setOnEventListener(@Nullable OnEventListener listener) { synchronized(mEventLock) { mOnEventListener = listener; } } /** * Interface definition for a callback to be invoked when a drm event Loading Loading @@ -513,6 +616,7 @@ public final class MediaDrm implements AutoCloseable { 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 { Loading @@ -532,6 +636,7 @@ public final class MediaDrm implements AutoCloseable { switch(msg.what) { case DRM_EVENT: synchronized(mEventLock) { if (mOnEventListener != null) { if (msg.obj != null && msg.obj instanceof Parcel) { Parcel parcel = (Parcel)msg.obj; Loading @@ -548,9 +653,11 @@ public final class MediaDrm implements AutoCloseable { mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data); } } } return; case KEY_STATUS_CHANGE: synchronized(mKeyStatusChangeLock) { if (mOnKeyStatusChangeListener != null) { if (msg.obj != null && msg.obj instanceof Parcel) { Parcel parcel = (Parcel)msg.obj; Loading @@ -565,9 +672,11 @@ public final class MediaDrm implements AutoCloseable { } } } } return; case EXPIRATION_UPDATE: synchronized(mExpirationUpdateLock) { if (mOnExpirationUpdateListener != null) { if (msg.obj != null && msg.obj instanceof Parcel) { Parcel parcel = (Parcel)msg.obj; Loading @@ -581,6 +690,21 @@ public final class MediaDrm implements AutoCloseable { } } } } 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); } } } return; default: Loading Loading @@ -619,11 +743,44 @@ public final class MediaDrm implements AutoCloseable { if (md == null) { return; } 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; } } /** * Open a new session with the MediaDrm object. A session ID is returned. Loading
media/jni/android_media_MediaCodec.cpp +30 −0 Original line number Diff line number Diff line Loading @@ -72,7 +72,10 @@ static struct CryptoErrorCodes { jint cryptoErrorResourceBusy; jint cryptoErrorInsufficientOutputProtection; jint cryptoErrorSessionNotOpened; jint cryptoErrorInsufficientSecurity; jint cryptoErrorUnsupportedOperation; jint cryptoErrorFrameTooLarge; jint cryptoErrorLostState; } gCryptoErrorCodes; static struct CodecActionCodes { Loading Loading @@ -1005,10 +1008,22 @@ static void throwCryptoException(JNIEnv *env, status_t err, const char *msg) { 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; } Loading Loading @@ -1994,11 +2009,26 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) { 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"); Loading
media/jni/android_media_MediaDrm.cpp +53 −1 Original line number Diff line number Diff line Loading @@ -110,6 +110,7 @@ struct EventWhat { jint kWhatDrmEvent; jint kWhatExpirationUpdate; jint kWhatKeyStatusChange; jint kWhatSessionLostState; } gEventWhat; struct KeyTypes { Loading Loading @@ -141,6 +142,16 @@ struct StateExceptionFields { jclass classId; }; struct SessionExceptionFields { jmethodID init; jclass classId; jfieldID errorCode; }; struct SessionExceptionErrorCodes { jint kResourceContention; } gSessionExceptionErrorCodes; struct HDCPLevels { jint kHdcpLevelUnknown; jint kHdcpNone; Loading Loading @@ -180,6 +191,7 @@ struct fields_t { EntryFields entry; CertificateFields certificate; StateExceptionFields stateException; SessionExceptionFields sessionException; jclass certificateClassId; jclass hashmapClassId; jclass arraylistClassId; Loading Loading @@ -310,6 +322,9 @@ void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra, 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; Loading Loading @@ -343,6 +358,30 @@ static void throwStateException(JNIEnv *env, const char *msg, status_t err) { 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) { Loading Loading @@ -370,7 +409,7 @@ static bool throwExceptionAsNecessary( 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: Loading Loading @@ -399,6 +438,9 @@ static bool throwExceptionAsNecessary( 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) { Loading Loading @@ -705,6 +747,8 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) { 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); Loading Loading @@ -831,6 +875,14 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) { 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( Loading