}
public final class MediaCas {
- ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+ ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
method public void closeSession(byte[]);
method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
method public static boolean isSystemIdSupported(int);
- method public byte[] openSession(int);
- method public byte[] openSession(int, int);
- method public void processEcm(byte[], byte[], int, int);
- method public void processEcm(byte[], byte[]);
- method public void processEmm(byte[], int, int);
- method public void processEmm(byte[]);
- method public void provision(java.lang.String);
- method public void refreshEntitlements(int, byte[]);
+ method public byte[] openSession(int) throws android.media.MediaCasException;
+ method public byte[] openSession(int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
+ method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
+ method public void processEmm(byte[]) throws android.media.MediaCasException;
+ method public void provision(java.lang.String) throws android.media.MediaCasException;
+ method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
method public void release();
- method public void sendEvent(int, int, byte[]);
+ method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
- method public void setPrivateData(byte[]);
- method public void setSessionPrivateData(byte[], byte[]);
+ method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+ method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
}
public static abstract interface MediaCas.EventListener {
}
public class MediaCasException extends java.lang.Exception {
- ctor public MediaCasException(java.lang.String);
+ }
+
+ public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.ResourceBusyException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.UnsupportedCasException extends android.media.MediaCasException {
+ }
+
+ public class MediaCasStateException extends java.lang.IllegalStateException {
+ method public java.lang.String getDiagnosticInfo();
}
public final class MediaCodec {
}
public final class MediaDescrambler {
- ctor public MediaDescrambler(int) throws android.media.UnsupportedCasException;
+ ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
field public static final int TONE_SUP_RINGTONE = 23; // 0x17
}
- public final class UnsupportedCasException extends android.media.MediaCasException {
- ctor public UnsupportedCasException(java.lang.String);
- }
-
public final class UnsupportedSchemeException extends android.media.MediaDrmException {
ctor public UnsupportedSchemeException(java.lang.String);
}
}
public final class MediaCas {
- ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+ ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
method public void closeSession(byte[]);
method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
method public static boolean isSystemIdSupported(int);
- method public byte[] openSession(int);
- method public byte[] openSession(int, int);
- method public void processEcm(byte[], byte[], int, int);
- method public void processEcm(byte[], byte[]);
- method public void processEmm(byte[], int, int);
- method public void processEmm(byte[]);
- method public void provision(java.lang.String);
- method public void refreshEntitlements(int, byte[]);
+ method public byte[] openSession(int) throws android.media.MediaCasException;
+ method public byte[] openSession(int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
+ method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
+ method public void processEmm(byte[]) throws android.media.MediaCasException;
+ method public void provision(java.lang.String) throws android.media.MediaCasException;
+ method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
method public void release();
- method public void sendEvent(int, int, byte[]);
+ method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
- method public void setPrivateData(byte[]);
- method public void setSessionPrivateData(byte[], byte[]);
+ method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+ method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
}
public static abstract interface MediaCas.EventListener {
}
public class MediaCasException extends java.lang.Exception {
- ctor public MediaCasException(java.lang.String);
+ }
+
+ public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.ResourceBusyException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.UnsupportedCasException extends android.media.MediaCasException {
+ }
+
+ public class MediaCasStateException extends java.lang.IllegalStateException {
+ method public java.lang.String getDiagnosticInfo();
}
public final class MediaCodec {
}
public final class MediaDescrambler {
- ctor public MediaDescrambler(int) throws android.media.UnsupportedCasException;
+ ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
field public static final int TONE_SUP_RINGTONE = 23; // 0x17
}
- public final class UnsupportedCasException extends android.media.MediaCasException {
- ctor public UnsupportedCasException(java.lang.String);
- }
-
public final class UnsupportedSchemeException extends android.media.MediaDrmException {
ctor public UnsupportedSchemeException(java.lang.String);
}
}
public final class MediaCas {
- ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+ ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
method public void closeSession(byte[]);
method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
method public static boolean isSystemIdSupported(int);
- method public byte[] openSession(int);
- method public byte[] openSession(int, int);
- method public void processEcm(byte[], byte[], int, int);
- method public void processEcm(byte[], byte[]);
- method public void processEmm(byte[], int, int);
- method public void processEmm(byte[]);
- method public void provision(java.lang.String);
- method public void refreshEntitlements(int, byte[]);
+ method public byte[] openSession(int) throws android.media.MediaCasException;
+ method public byte[] openSession(int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
+ method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
+ method public void processEmm(byte[]) throws android.media.MediaCasException;
+ method public void provision(java.lang.String) throws android.media.MediaCasException;
+ method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
method public void release();
- method public void sendEvent(int, int, byte[]);
+ method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
- method public void setPrivateData(byte[]);
- method public void setSessionPrivateData(byte[], byte[]);
+ method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+ method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
}
public static abstract interface MediaCas.EventListener {
}
public class MediaCasException extends java.lang.Exception {
- ctor public MediaCasException(java.lang.String);
+ }
+
+ public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.ResourceBusyException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.UnsupportedCasException extends android.media.MediaCasException {
+ }
+
+ public class MediaCasStateException extends java.lang.IllegalStateException {
+ method public java.lang.String getDiagnosticInfo();
}
public final class MediaCodec {
}
public final class MediaDescrambler {
- ctor public MediaDescrambler(int) throws android.media.UnsupportedCasException;
+ ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
field public static final int TONE_SUP_RINGTONE = 23; // 0x17
}
- public final class UnsupportedCasException extends android.media.MediaCasException {
- ctor public UnsupportedCasException(java.lang.String);
- }
-
public final class UnsupportedSchemeException extends android.media.MediaDrmException {
ctor public UnsupportedSchemeException(java.lang.String);
}
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.media.MediaCasException.*;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.util.Log;
import android.util.Singleton;
* sessionId of the descrambler can be retrieved by {@link MediaExtractor#getDrmInitData}
* and used to initialize a MediaDescrambler object for MediaCodec.
* <p>
- * TODO: determine exception handling schemes.
- * <p>
* <h3>Listeners</h3>
* <p>The app may register a listener to receive events from the CA system using
* method {@link #setEventListener}. The exact format of the event is scheme-specific
mEventHandler = new EventHandler(looper);
}
- /*
- * TODO: handle ServiceSpecificException from the IMediaCas
- * All Drm-specific failures will be thrown by mICas as
- * ServiceSpecificException exception with Drm error code.
- * These need to be re-thrown as crypto exceptions.
- */
-
/**
* Send the private data for the CA system.
*
* @param data byte array of the private data.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void setPrivateData(@NonNull byte[] data) {
+ public void setPrivateData(@NonNull byte[] data) throws MediaCasException {
validateInternalStates();
try {
mICas.setPrivateData(data);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
*
* @return session id of the newly opened session.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if a session for the program already exists.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public byte[] openSession(int programNumber) {
+ public byte[] openSession(int programNumber) throws MediaCasException {
validateInternalStates();
try {
return mICas.openSession(programNumber);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
*
* @return session id of the newly opened session.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if a session for the stream already exists.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public byte[] openSession(int programNumber, int elementaryPID) {
+ public byte[] openSession(int programNumber, int elementaryPID)
+ throws MediaCasException {
validateInternalStates();
try {
return mICas.openSessionForStream(programNumber, elementaryPID);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
*
* @param sessionId the session to be closed.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if the session is not valid.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
public void closeSession(@NonNull byte[] sessionId) {
validateInternalStates();
try {
mICas.closeSession(sessionId);
+ } catch (ServiceSpecificException e) {
+ MediaCasStateException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
* @param sessionId the session for which the private data is intended.
* @param data byte array of the private data.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if the session is not valid.
- */
- /*
- * TODO: need to re-throw DRM-specific exceptions
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public void setSessionPrivateData(@NonNull byte[] sessionId, @NonNull byte[] data) {
+ public void setSessionPrivateData(@NonNull byte[] sessionId, @NonNull byte[] data)
+ throws MediaCasException {
validateInternalStates();
try {
mICas.setSessionPrivateData(sessionId, data);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
* @param offset position within data where the ECM data begins.
* @param length length of the data (starting from offset).
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if the session is not valid.
- */
- /*
- * TODO: need to re-throw DRM-specific exceptions
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public void processEcm(
- @NonNull byte[] sessionId, @NonNull byte[] data, int offset, int length) {
+ public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data,
+ int offset, int length) throws MediaCasException {
validateInternalStates();
try {
mCasData.set(data, offset, length);
mICas.processEcm(sessionId, mCasData);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
* @param sessionId the session for which the ECM is intended.
* @param data byte array of the ECM data.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if the session is not valid.
- */
- /*
- * TODO: need to re-throw DRM-specific exceptions
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data) {
+ public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data)
+ throws MediaCasException {
processEcm(sessionId, data, 0, data.length);
}
* @param length length of the data (starting from offset).
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void processEmm(@NonNull byte[] data, int offset, int length) {
+ public void processEmm(@NonNull byte[] data, int offset, int length)
+ throws MediaCasException {
validateInternalStates();
try {
mCasData.set(data, offset, length);
mICas.processEmm(mCasData);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
* @param data byte array of the EMM data.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void processEmm(@NonNull byte[] data) {
+ public void processEmm(@NonNull byte[] data) throws MediaCasException {
processEmm(data, 0, data.length);
}
* @param data a byte array containing scheme-specific data for the event.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public void sendEvent(int event, int arg, @Nullable byte[] data) {
+ public void sendEvent(int event, int arg, @Nullable byte[] data)
+ throws MediaCasException {
validateInternalStates();
try {
mICas.sendEvent(event, arg, data);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
* specific.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public void provision(@NonNull String provisionString) {
+ public void provision(@NonNull String provisionString) throws MediaCasException {
validateInternalStates();
try {
mICas.provision(provisionString);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
* @param refreshData private data associated with the refreshment.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: define enums for refreshType
- */
- public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData) {
+ public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData)
+ throws MediaCasException {
validateInternalStates();
try {
mICas.refreshEntitlements(refreshType, refreshData);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
package android.media;
+import android.os.ServiceSpecificException;
+
/**
* Base class for MediaCas exceptions
*/
public class MediaCasException extends Exception {
+
+ /** @hide */
+ public static final int DRM_ERROR_BASE = -2000;
+ /** @hide */
+ public static final int ERROR_DRM_UNKNOWN = DRM_ERROR_BASE;
+ /** @hide */
+ public static final int ERROR_DRM_NO_LICENSE = DRM_ERROR_BASE - 1;
+ /** @hide */
+ public static final int ERROR_DRM_LICENSE_EXPIRED = DRM_ERROR_BASE - 2;
+ /** @hide */
+ public static final int ERROR_DRM_SESSION_NOT_OPENED = DRM_ERROR_BASE - 3;
+ /** @hide */
+ public static final int ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED = DRM_ERROR_BASE - 4;
+ /** @hide */
+ public static final int ERROR_DRM_DECRYPT = DRM_ERROR_BASE - 5;
+ /** @hide */
+ public static final int ERROR_DRM_CANNOT_HANDLE = DRM_ERROR_BASE - 6;
+ /** @hide */
+ public static final int ERROR_DRM_TAMPER_DETECTED = DRM_ERROR_BASE - 7;
+ /** @hide */
+ public static final int ERROR_DRM_NOT_PROVISIONED = DRM_ERROR_BASE - 8;
+ /** @hide */
+ public static final int ERROR_DRM_DEVICE_REVOKED = DRM_ERROR_BASE - 9;
+ /** @hide */
+ public static final int ERROR_DRM_RESOURCE_BUSY = DRM_ERROR_BASE - 10;
+ /** @hide */
+ public static final int ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION = DRM_ERROR_BASE - 11;
+ /** @hide */
+ public static final int ERROR_DRM_LAST_USED_ERRORCODE = DRM_ERROR_BASE - 11;
+ /** @hide */
+ public static final int ERROR_DRM_VENDOR_MAX = DRM_ERROR_BASE - 500;
+ /** @hide */
+ public static final int ERROR_DRM_VENDOR_MIN = DRM_ERROR_BASE - 999;
+
+ /** @hide */
public MediaCasException(String detailMessage) {
super(detailMessage);
}
+
+ static void throwExceptions(ServiceSpecificException e) throws MediaCasException {
+ if (e.errorCode == ERROR_DRM_NOT_PROVISIONED) {
+ throw new NotProvisionedException(e.getMessage());
+ } else if (e.errorCode == ERROR_DRM_RESOURCE_BUSY) {
+ throw new ResourceBusyException(e.getMessage());
+ } else if (e.errorCode == ERROR_DRM_DEVICE_REVOKED) {
+ throw new DeniedByServerException(e.getMessage());
+ } else {
+ MediaCasStateException.throwExceptions(e);
+ }
+ }
+
+ /**
+ * Exception thrown when an attempt is made to construct a MediaCas object
+ * using a CA_system_id that is not supported by the device
+ */
+ public static final class UnsupportedCasException extends MediaCasException {
+ /** @hide */
+ public UnsupportedCasException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Exception thrown when an operation on a MediaCas object is attempted
+ * before it's provisioned successfully.
+ */
+ public static final class NotProvisionedException extends MediaCasException {
+ /** @hide */
+ public NotProvisionedException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Exception thrown when the provisioning server or key server denies a
+ * license for a device.
+ */
+ public static final class DeniedByServerException extends MediaCasException {
+ /** @hide */
+ public DeniedByServerException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Exception thrown when an operation on a MediaCas object is attempted
+ * and hardware resources are not available, due to being in use.
+ */
+ public static final class ResourceBusyException extends MediaCasException {
+ /** @hide */
+ public ResourceBusyException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ServiceSpecificException;
+
+import static android.media.MediaCasException.*;
+
+/**
+ * Base class for MediaCas runtime exceptions
+ */
+public class MediaCasStateException extends IllegalStateException {
+ private final int mErrorCode;
+ private final String mDiagnosticInfo;
+
+ /** @hide */
+ public MediaCasStateException(int err, @Nullable String msg, @Nullable String diagnosticInfo) {
+ super(msg);
+ mErrorCode = err;
+ mDiagnosticInfo = diagnosticInfo;
+ }
+
+ static void throwExceptions(ServiceSpecificException e) {
+ String diagnosticInfo = "";
+ switch (e.errorCode) {
+ case ERROR_DRM_UNKNOWN:
+ diagnosticInfo = "General CAS error";
+ break;
+ case ERROR_DRM_NO_LICENSE:
+ diagnosticInfo = "No license";
+ break;
+ case ERROR_DRM_LICENSE_EXPIRED:
+ diagnosticInfo = "License expired";
+ break;
+ case ERROR_DRM_SESSION_NOT_OPENED:
+ diagnosticInfo = "Session not opened";
+ break;
+ case ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED:
+ diagnosticInfo = "Not initialized";
+ break;
+ case ERROR_DRM_DECRYPT:
+ diagnosticInfo = "Decrypt error";
+ break;
+ case ERROR_DRM_CANNOT_HANDLE:
+ diagnosticInfo = "Unsupported scheme or data format";
+ break;
+ case ERROR_DRM_TAMPER_DETECTED:
+ diagnosticInfo = "Tamper detected";
+ break;
+ default:
+ diagnosticInfo = "Unknown CAS state exception";
+ break;
+ }
+ throw new MediaCasStateException(e.errorCode, e.getMessage(),
+ String.format("%s (err=%d)", diagnosticInfo, e.errorCode));
+ }
+
+ /**
+ * Retrieve the associated error code
+ *
+ * @hide
+ */
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /**
+ * Retrieve a developer-readable diagnostic information string
+ * associated with the exception. Do not show this to end-users,
+ * since this string will not be localized or generally comprehensible
+ * to end-users.
+ */
+ @NonNull
+ public String getDiagnosticInfo() {
+ return mDiagnosticInfo;
+ }
+}
* @param flags Specify {@link #CONFIGURE_FLAG_ENCODE} to configure the
* component as an encoder.
* @param descrambler Specify a descrambler object to facilitate secure
- * descrambling of the media data. descrambler must not be
- * null if this method is used. For non-secure codecs, use
- * {@link #configure} and with null crypto parameter.
+ * descrambling of the media data, or null for non-secure codecs.
* @throws IllegalArgumentException if the surface has been released (or is invalid),
* or the format is unacceptable (e.g. missing a mandatory key),
* or the flags are not set properly
*/
public void configure(
@Nullable MediaFormat format, @Nullable Surface surface,
- @ConfigureFlag int flags, @NonNull MediaDescrambler descrambler) {
- configure(format, surface, null, descrambler.getBinder(), flags);
+ @ConfigureFlag int flags, @Nullable MediaDescrambler descrambler) {
+ configure(format, surface, null,
+ descrambler != null ? descrambler.getBinder() : null, flags);
}
private void configure(
package android.media;
import android.annotation.NonNull;
+import android.media.MediaCasException.UnsupportedCasException;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.util.Log;
import java.nio.ByteBuffer;
/**
* Class for parceling descrambling parameters over IDescrambler binder.
*/
+ // This class currently is not used by Java binder. descramble() goes through
+ // jni to use shared memory. However, the parcelable is still required for AIDL.
static class DescrambleInfo implements Parcelable {
private DescrambleInfo() {
- // TODO: implement
}
private DescrambleInfo(Parcel in) {
- // TODO: disable
}
@Override
@Override
public void writeToParcel(Parcel dest, int flags) {
- // TODO: implement
}
public static final Parcelable.Creator<DescrambleInfo> CREATOR
return mIDescrambler.asBinder();
}
- /*
- * TODO: handle ServiceSpecificException from the mIDescrambler
- * All Drm-specific failures will be thrown by mIDescrambler as
- * ServiceSpecificException exception with Drm error code.
- * These need to be re-thrown as crypto exceptions.
- */
-
/**
* Query if the scrambling scheme requires the use of a secure decoder
* to decode data of the given mime type.
* @param sessionId the MediaCas sessionId to associate with this
* MediaDescrambler instance.
*
- * @throws IllegalStateException if the descrambler instance is not valid,
- * or IllegalArgumentException if the sessionId is not valid.
+ * @throws IllegalStateException if the descrambler instance is not valid.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
public final void setMediaCasSession(@NonNull byte[] sessionId) {
validateInternalStates();
try {
mIDescrambler.setMediaCasSession(sessionId);
+ } catch (ServiceSpecificException e) {
+ MediaCasStateException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
* values indicating errors.
*
* @throws IllegalStateException if the descrambler instance is not valid.
- */
- /*
- * TODO: throw DRM-specific exception if decrambling is failing.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
public final int descramble(
@NonNull ByteBuffer srcBuf, int srcPos, ByteBuffer dstBuf, int dstPos,
"Invalid CryptoInfo: key array is invalid!");
}
- return native_descramble(
- cryptoInfo.key[0],
- cryptoInfo.numSubSamples,
- cryptoInfo.numBytesOfClearData,
- cryptoInfo.numBytesOfEncryptedData,
- srcBuf, srcPos, dstBuf, dstPos);
+ try {
+ return native_descramble(
+ cryptoInfo.key[0],
+ cryptoInfo.numSubSamples,
+ cryptoInfo.numBytesOfClearData,
+ cryptoInfo.numBytesOfEncryptedData,
+ srcBuf, srcPos, dstBuf, dstPos);
+ } catch (ServiceSpecificException e) {
+ MediaCasStateException.throwExceptions(e);
+ }
+ return -1;
}
public final void release() {
+++ /dev/null
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-/**
- * Exception thrown when an attempt is made to construct a MediaCas object
- * using a CA_system_id that is not supported by the device
- */
-public final class UnsupportedCasException extends MediaCasException {
- public UnsupportedCasException(String detailMessage) {
- super(detailMessage);
- }
-}
mMem = mDealer->allocate(neededSize);
}
-ssize_t JDescrambler::descramble(
+Status JDescrambler::descramble(
jbyte key,
size_t numSubSamples,
ssize_t totalLength,
const void *srcPtr,
jint srcOffset,
void *dstPtr,
- jint dstOffset) {
+ jint dstOffset,
+ ssize_t *result) {
// TODO: IDescrambler::descramble() is re-entrant, however because we
// only have 1 shared mem buffer, we can only do 1 descramble at a time.
// Concurrency might be improved by allowing on-demand allocation of up
info.dstPtr = NULL;
info.dstOffset = 0;
- int32_t result;
- binder::Status status = mDescrambler->descramble(info, &result);
+ int32_t descrambleResult;
+ Status status = mDescrambler->descramble(info, &descrambleResult);
- if (!status.isOk() || result > totalLength) {
- return -1;
- }
- if (result > 0) {
- memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), result);
+ if (status.isOk()) {
+ *result = (descrambleResult <= totalLength) ? descrambleResult : -1;
+ if (*result > 0) {
+ memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), *result);
+ }
}
- return result;
+ return status;
}
} // namespace android
numBytesOfClearData = NULL;
}
+ if (totalSize < 0) {
+ delete[] subSamples;
+ return -1;
+ }
+
*outSubSamples = subSamples;
return totalSize;
}
+static jthrowable createServiceSpecificException(
+ JNIEnv *env, int serviceSpecificError, const char *msg) {
+ if (env->ExceptionCheck()) {
+ ALOGW("Discarding pending exception");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+
+ ScopedLocalRef<jclass> clazz(
+ env, env->FindClass("android/os/ServiceSpecificException"));
+ CHECK(clazz.get() != NULL);
+
+ const jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(ILjava/lang/String;)V");
+ CHECK(ctor != NULL);
+
+ ScopedLocalRef<jstring> msgObj(
+ env, env->NewStringUTF(msg != NULL ?
+ msg : String8::format("Error %#x", serviceSpecificError)));
+
+ return (jthrowable)env->NewObject(
+ clazz.get(), ctor, serviceSpecificError, msgObj.get());
+}
+
+static void throwServiceSpecificException(
+ JNIEnv *env, int serviceSpecificError, const char *msg) {
+ jthrowable exception = createServiceSpecificException(env, serviceSpecificError, msg);
+ env->Throw(exception);
+}
+
static jint android_media_MediaDescrambler_native_descramble(
JNIEnv *env, jobject thiz, jbyte key, jint numSubSamples,
jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj,
env, dstBuf, dstOffset, totalLength, &dstPtr, &dstArray);
}
}
-
+ Status status;
if (err == OK) {
- result = descrambler->descramble(
+ status = descrambler->descramble(
key, numSubSamples, totalLength, subSamples,
- srcPtr, srcOffset, dstPtr, dstOffset);
+ srcPtr, srcOffset, dstPtr, dstOffset, &result);
}
delete[] subSamples;
if (dstArray != NULL) {
env->ReleaseByteArrayElements(dstArray, (jbyte *)dstPtr, 0);
}
+
+ if (!status.isOk()) {
+ switch (status.exceptionCode()) {
+ case Status::EX_SECURITY:
+ jniThrowException(env, "java/lang/SecurityException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_BAD_PARCELABLE:
+ jniThrowException(env, "java/lang/BadParcelableException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_ILLEGAL_ARGUMENT:
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_NULL_POINTER:
+ jniThrowException(env, "java/lang/NullPointerException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_ILLEGAL_STATE:
+ jniThrowException(env, "java/lang/IllegalStateException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_NETWORK_MAIN_THREAD:
+ jniThrowException(env, "java/lang/NetworkOnMainThreadException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_UNSUPPORTED_OPERATION:
+ jniThrowException(env, "java/lang/UnsupportedOperationException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_SERVICE_SPECIFIC:
+ throwServiceSpecificException(env, status.serviceSpecificErrorCode(),
+ status.exceptionMessage());
+ break;
+ default:
+ {
+ String8 msg;
+ msg.appendFormat("Unknown exception code: %d, msg: %s",
+ status.exceptionCode(), status.exceptionMessage().string());
+ jniThrowException(env, "java/lang/RuntimeException", msg.string());
+ break;
+ }
+ }
+ }
return result;
}
#include "jni.h"
+#include <binder/Status.h>
#include <media/cas/DescramblerAPI.h>
#include <media/stagefright/foundation/ABase.h>
#include <utils/Mutex.h>
class IDescrambler;
};
using namespace media;
+using binder::Status;
struct JDescrambler : public RefBase {
JDescrambler(JNIEnv *env, jobject descramberBinderObj);
- ssize_t descramble(
+ Status descramble(
jbyte key,
size_t numSubSamples,
ssize_t totalLength,
const void *srcPtr,
jint srcOffset,
void *dstPtr,
- jint dstOffset);
+ jint dstOffset,
+ ssize_t *result);
protected:
virtual ~JDescrambler();