OSDN Git Service

MediaCas: rethrow MediaCas specific exception
authorChong Zhang <chz@google.com>
Tue, 14 Mar 2017 17:05:36 +0000 (10:05 -0700)
committerChong Zhang <chz@google.com>
Fri, 24 Mar 2017 21:28:43 +0000 (14:28 -0700)
Translate ServiceSpecificException from aidl to CAS-specific
exceptions. Also, throw CAS specific exception for descramble().

bug: 22804304

Change-Id: I6d68c335c87fbb95fd8cc227e432eed4e5951550

api/current.txt
api/system-current.txt
api/test-current.txt
media/java/android/media/MediaCas.java
media/java/android/media/MediaCasException.java
media/java/android/media/MediaCasStateException.java [new file with mode: 0644]
media/java/android/media/MediaCodec.java
media/java/android/media/MediaDescrambler.java
media/java/android/media/UnsupportedCasException.java [deleted file]
media/jni/android_media_MediaDescrambler.cpp
media/jni/android_media_MediaDescrambler.h

index 90c48fb..581862d 100644 (file)
@@ -21797,23 +21797,23 @@ package android.media {
   }
 
   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 {
@@ -21826,7 +21826,22 @@ package android.media {
   }
 
   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 {
@@ -22254,7 +22269,7 @@ package android.media {
   }
 
   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);
@@ -23565,10 +23580,6 @@ package android.media {
     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);
   }
index 0d54bee..c273f9d 100644 (file)
@@ -23588,23 +23588,23 @@ package android.media {
   }
 
   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 {
@@ -23617,7 +23617,22 @@ package android.media {
   }
 
   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 {
@@ -24045,7 +24060,7 @@ package android.media {
   }
 
   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);
@@ -25367,10 +25382,6 @@ package android.media {
     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);
   }
index e56255c..9c067bc 100644 (file)
@@ -21898,23 +21898,23 @@ package android.media {
   }
 
   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 {
@@ -21927,7 +21927,22 @@ package android.media {
   }
 
   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 {
@@ -22355,7 +22370,7 @@ package android.media {
   }
 
   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);
@@ -23666,10 +23681,6 @@ package android.media {
     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);
   }
index 2e22132..611fdd1 100644 (file)
@@ -18,6 +18,7 @@ package android.media;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.media.MediaCasException.*;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -28,6 +29,7 @@ import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 import android.util.Singleton;
 
@@ -84,8 +86,6 @@ 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
@@ -382,28 +382,22 @@ public final class MediaCas {
         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();
         }
@@ -416,14 +410,17 @@ public final class MediaCas {
      *
      * @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();
         }
@@ -438,14 +435,18 @@ public final class MediaCas {
      *
      * @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();
         }
@@ -457,14 +458,16 @@ public final class MediaCas {
      *
      * @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();
         }
@@ -476,17 +479,18 @@ public final class MediaCas {
      * @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();
         }
@@ -500,19 +504,19 @@ public final class MediaCas {
      * @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();
         }
@@ -526,13 +530,12 @@ public final class MediaCas {
      * @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);
     }
 
@@ -544,16 +547,18 @@ public final class MediaCas {
      * @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();
         }
@@ -567,11 +572,10 @@ public final class MediaCas {
      * @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);
     }
 
@@ -584,12 +588,17 @@ public final class MediaCas {
      * @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();
         }
@@ -603,12 +612,16 @@ public final class MediaCas {
      * 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();
         }
@@ -621,15 +634,17 @@ public final class MediaCas {
      * @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();
         }
index 1d5d3cd..485f6ee 100644 (file)
 
 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);
+        }
+    }
 }
diff --git a/media/java/android/media/MediaCasStateException.java b/media/java/android/media/MediaCasStateException.java
new file mode 100644 (file)
index 0000000..cf05c29
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * 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;
+    }
+}
index 13a22b4..e628d18 100644 (file)
@@ -1878,9 +1878,7 @@ final public class MediaCodec {
      * @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
@@ -1891,8 +1889,9 @@ final public class MediaCodec {
      */
     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(
index f5eede8..2dd1097 100644 (file)
 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;
@@ -54,13 +56,13 @@ public final class MediaDescrambler {
     /**
      * 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
@@ -70,7 +72,6 @@ public final class MediaDescrambler {
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            // TODO: implement
         }
 
         public static final Parcelable.Creator<DescrambleInfo> CREATOR
@@ -112,13 +113,6 @@ public final class MediaDescrambler {
         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.
@@ -150,14 +144,16 @@ public final class MediaDescrambler {
      * @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();
         }
@@ -179,9 +175,7 @@ public final class MediaDescrambler {
      * 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,
@@ -208,12 +202,17 @@ public final class MediaDescrambler {
                     "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() {
diff --git a/media/java/android/media/UnsupportedCasException.java b/media/java/android/media/UnsupportedCasException.java
deleted file mode 100644 (file)
index 3167637..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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);
-    }
-}
index 7585664..f031dbb 100644 (file)
@@ -129,7 +129,7 @@ void JDescrambler::ensureBufferCapacity(size_t neededSize) {
     mMem = mDealer->allocate(neededSize);
 }
 
-ssize_t JDescrambler::descramble(
+Status JDescrambler::descramble(
         jbyte key,
         size_t numSubSamples,
         ssize_t totalLength,
@@ -137,7 +137,8 @@ ssize_t JDescrambler::descramble(
         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
@@ -159,16 +160,16 @@ ssize_t JDescrambler::descramble(
     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
@@ -251,11 +252,45 @@ static ssize_t getSubSampleInfo(JNIEnv *env, jint numSubSamples,
         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,
@@ -290,11 +325,11 @@ static jint android_media_MediaDescrambler_native_descramble(
                     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;
@@ -304,6 +339,51 @@ static jint android_media_MediaDescrambler_native_descramble(
     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;
 }
 
index e944a90..aeef05e 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "jni.h"
 
+#include <binder/Status.h>
 #include <media/cas/DescramblerAPI.h>
 #include <media/stagefright/foundation/ABase.h>
 #include <utils/Mutex.h>
@@ -31,11 +32,12 @@ namespace media {
 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,
@@ -43,7 +45,8 @@ struct JDescrambler : public RefBase {
             const void *srcPtr,
             jint srcOffset,
             void *dstPtr,
-            jint dstOffset);
+            jint dstOffset,
+            ssize_t *result);
 
 protected:
     virtual ~JDescrambler();