OSDN Git Service

MediaCas: address comments for API reviews
authorChong Zhang <chz@google.com>
Fri, 31 Mar 2017 21:52:52 +0000 (14:52 -0700)
committerChong Zhang <chz@google.com>
Tue, 11 Apr 2017 20:13:22 +0000 (13:13 -0700)
- Wrap session id byte array in Session object

- Move session operations from MediaCas to Session

- Remove position prarameters on descramble() method

- Retrieve cas info for a track by getCasInfo() instead
  of getDrmInitData().

bug: 22804304
bug: 36791613
bug: 36783335

Change-Id: Ib3ad8d6a2f679c0e60d2bb025ac5999339722306

api/current.txt
api/system-current.txt
api/test-current.txt
media/java/android/media/MediaCas.java
media/java/android/media/MediaDescrambler.java
media/java/android/media/MediaExtractor.java
media/java/android/media/MediaFormat.java
media/jni/android_media_MediaDescrambler.cpp

index 7c532d4..e0eae2c 100644 (file)
@@ -21818,24 +21818,20 @@ package android.media {
     field public static final int STOP_VIDEO_RECORDING = 3; // 0x3
   }
 
-  public final class MediaCas {
+  public final class MediaCas implements java.lang.AutoCloseable {
     ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
-    method public void closeSession(byte[]);
+    method public void close();
     method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
     method public static boolean isSystemIdSupported(int);
-    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 android.media.MediaCas.Session openSession(int) throws android.media.MediaCasException;
+    method public android.media.MediaCas.Session openSession(int, int) 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[]) throws android.media.MediaCasException;
     method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
     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 {
@@ -21847,6 +21843,13 @@ package android.media {
     method public int getSystemId();
   }
 
+  public final class MediaCas.Session implements java.lang.AutoCloseable {
+    method public void close();
+    method public void processEcm(byte[], int, int) throws android.media.MediaCasException;
+    method public void processEcm(byte[]) throws android.media.MediaCasException;
+    method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+  }
+
   public class MediaCasException extends java.lang.Exception {
   }
 
@@ -22290,12 +22293,12 @@ package android.media {
     method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
   }
 
-  public final class MediaDescrambler {
+  public final class MediaDescrambler implements java.lang.AutoCloseable {
     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 void close();
+    method public final int descramble(java.nio.ByteBuffer, java.nio.ByteBuffer, android.media.MediaCodec.CryptoInfo);
     method public final boolean requiresSecureDecoderComponent(java.lang.String);
-    method public final void setMediaCasSession(byte[]);
+    method public final void setMediaCasSession(android.media.MediaCas.Session);
   }
 
   public class MediaDescription implements android.os.Parcelable {
@@ -22433,6 +22436,7 @@ package android.media {
     ctor public MediaExtractor();
     method public boolean advance();
     method public long getCachedDuration();
+    method public android.media.MediaExtractor.CasInfo getCasInfo(int);
     method public android.media.DrmInitData getDrmInitData();
     method public android.media.MediaMetricsSet getMetrics();
     method public java.util.Map<java.util.UUID, byte[]> getPsshInfo();
@@ -22464,6 +22468,11 @@ package android.media {
     field public static final int SEEK_TO_PREVIOUS_SYNC = 0; // 0x0
   }
 
+  public static final class MediaExtractor.CasInfo {
+    method public android.media.MediaCas.Session getSession();
+    method public int getSystemId();
+  }
+
   public final class MediaFormat {
     ctor public MediaFormat();
     method public final boolean containsKey(java.lang.String);
index 99a5793..13e2093 100644 (file)
@@ -23643,24 +23643,20 @@ package android.media {
     field public static final int STOP_VIDEO_RECORDING = 3; // 0x3
   }
 
-  public final class MediaCas {
+  public final class MediaCas implements java.lang.AutoCloseable {
     ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
-    method public void closeSession(byte[]);
+    method public void close();
     method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
     method public static boolean isSystemIdSupported(int);
-    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 android.media.MediaCas.Session openSession(int) throws android.media.MediaCasException;
+    method public android.media.MediaCas.Session openSession(int, int) 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[]) throws android.media.MediaCasException;
     method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
     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 {
@@ -23672,6 +23668,13 @@ package android.media {
     method public int getSystemId();
   }
 
+  public final class MediaCas.Session implements java.lang.AutoCloseable {
+    method public void close();
+    method public void processEcm(byte[], int, int) throws android.media.MediaCasException;
+    method public void processEcm(byte[]) throws android.media.MediaCasException;
+    method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+  }
+
   public class MediaCasException extends java.lang.Exception {
   }
 
@@ -24115,12 +24118,12 @@ package android.media {
     method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
   }
 
-  public final class MediaDescrambler {
+  public final class MediaDescrambler implements java.lang.AutoCloseable {
     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 void close();
+    method public final int descramble(java.nio.ByteBuffer, java.nio.ByteBuffer, android.media.MediaCodec.CryptoInfo);
     method public final boolean requiresSecureDecoderComponent(java.lang.String);
-    method public final void setMediaCasSession(byte[]);
+    method public final void setMediaCasSession(android.media.MediaCas.Session);
   }
 
   public class MediaDescription implements android.os.Parcelable {
@@ -24258,6 +24261,7 @@ package android.media {
     ctor public MediaExtractor();
     method public boolean advance();
     method public long getCachedDuration();
+    method public android.media.MediaExtractor.CasInfo getCasInfo(int);
     method public android.media.DrmInitData getDrmInitData();
     method public android.media.MediaMetricsSet getMetrics();
     method public java.util.Map<java.util.UUID, byte[]> getPsshInfo();
@@ -24289,6 +24293,11 @@ package android.media {
     field public static final int SEEK_TO_PREVIOUS_SYNC = 0; // 0x0
   }
 
+  public static final class MediaExtractor.CasInfo {
+    method public android.media.MediaCas.Session getSession();
+    method public int getSystemId();
+  }
+
   public final class MediaFormat {
     ctor public MediaFormat();
     method public final boolean containsKey(java.lang.String);
index 570855f..b8d7dc1 100644 (file)
@@ -21931,24 +21931,20 @@ package android.media {
     field public static final int STOP_VIDEO_RECORDING = 3; // 0x3
   }
 
-  public final class MediaCas {
+  public final class MediaCas implements java.lang.AutoCloseable {
     ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
-    method public void closeSession(byte[]);
+    method public void close();
     method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
     method public static boolean isSystemIdSupported(int);
-    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 android.media.MediaCas.Session openSession(int) throws android.media.MediaCasException;
+    method public android.media.MediaCas.Session openSession(int, int) 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[]) throws android.media.MediaCasException;
     method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
     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 {
@@ -21960,6 +21956,13 @@ package android.media {
     method public int getSystemId();
   }
 
+  public final class MediaCas.Session implements java.lang.AutoCloseable {
+    method public void close();
+    method public void processEcm(byte[], int, int) throws android.media.MediaCasException;
+    method public void processEcm(byte[]) throws android.media.MediaCasException;
+    method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+  }
+
   public class MediaCasException extends java.lang.Exception {
   }
 
@@ -22403,12 +22406,12 @@ package android.media {
     method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
   }
 
-  public final class MediaDescrambler {
+  public final class MediaDescrambler implements java.lang.AutoCloseable {
     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 void close();
+    method public final int descramble(java.nio.ByteBuffer, java.nio.ByteBuffer, android.media.MediaCodec.CryptoInfo);
     method public final boolean requiresSecureDecoderComponent(java.lang.String);
-    method public final void setMediaCasSession(byte[]);
+    method public final void setMediaCasSession(android.media.MediaCas.Session);
   }
 
   public class MediaDescription implements android.os.Parcelable {
@@ -22546,6 +22549,7 @@ package android.media {
     ctor public MediaExtractor();
     method public boolean advance();
     method public long getCachedDuration();
+    method public android.media.MediaExtractor.CasInfo getCasInfo(int);
     method public android.media.DrmInitData getDrmInitData();
     method public android.media.MediaMetricsSet getMetrics();
     method public java.util.Map<java.util.UUID, byte[]> getPsshInfo();
@@ -22577,6 +22581,11 @@ package android.media {
     field public static final int SEEK_TO_PREVIOUS_SYNC = 0; // 0x0
   }
 
+  public static final class MediaExtractor.CasInfo {
+    method public android.media.MediaCas.Session getSession();
+    method public int getSystemId();
+  }
+
   public final class MediaFormat {
     ctor public MediaFormat();
     method public final boolean containsKey(java.lang.String);
index 611fdd1..4aae3d2 100644 (file)
@@ -51,12 +51,13 @@ import android.util.Singleton;
  * management messages) can be distributed out-of-band, or in-band with the stream.
  * <p>
  * To descramble elementary streams, the app first calls {@link #openSession} to
- * generate a sessionId that will uniquely identify a session. A session provides
- * a context for subsequent key updates and descrambling activities. The ECMs
- * (Entitlement control messages) are sent to the session via method {@link #processEcm}.
+ * generate a {@link Session} object that will uniquely identify a session. A session
+ * provides a context for subsequent key updates and descrambling activities. The ECMs
+ * (Entitlement control messages) are sent to the session via method
+ * {@link Session#processEcm}.
  * <p>
  * The app next constructs a MediaDescrambler object, and initializes it with the
- * sessionId using {@link MediaDescrambler#setMediaCasSession}. This ties the
+ * session using {@link MediaDescrambler#setMediaCasSession}. This ties the
  * descrambler to the session, and the descrambler can then be used to descramble
  * content secured with the session's key, either during extraction, or during decoding
  * with {@link android.media.MediaCodec}.
@@ -79,19 +80,20 @@ import android.util.Singleton;
  * If the app uses {@link MediaExtractor}, it can delegate the CAS session
  * management to MediaExtractor by calling {@link MediaExtractor#setMediaCas}.
  * MediaExtractor will take over and call {@link #openSession}, {@link #processEmm}
- * and/or {@link #processEcm}, etc.. if necessary.
+ * and/or {@link Session#processEcm}, etc.. if necessary.
  * <p>
  * When using {@link MediaExtractor}, the app would still need a MediaDescrambler
  * to use with {@link MediaCodec} if the licensing requires a secure decoder. The
- * sessionId of the descrambler can be retrieved by {@link MediaExtractor#getDrmInitData}
- * and used to initialize a MediaDescrambler object for MediaCodec.
+ * session associated with the descrambler of a track can be retrieved by calling
+ * {@link MediaExtractor#getCasInfo}, and used to initialize a MediaDescrambler
+ * object for MediaCodec.
  * <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
  * and is not specified by this API.
  */
-public final class MediaCas {
+public final class MediaCas implements AutoCloseable {
     private static final String TAG = "MediaCas";
     private final ParcelableCasData mCasData = new ParcelableCasData();
     private ICas mICas;
@@ -229,6 +231,106 @@ public final class MediaCas {
     }
 
     /**
+     * Class for an open session with the CA system.
+     */
+    public final class Session implements AutoCloseable {
+        final byte[] mSessionId;
+
+        Session(@NonNull byte[] sessionId) {
+            mSessionId = sessionId;
+        }
+
+        /**
+         * Set the private data for a session.
+         *
+         * @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.
+         */
+        public void setPrivateData(@NonNull byte[] data)
+                throws MediaCasException {
+            validateInternalStates();
+
+            try {
+                mICas.setSessionPrivateData(mSessionId, data);
+            } catch (ServiceSpecificException e) {
+                MediaCasException.throwExceptions(e);
+            } catch (RemoteException e) {
+                cleanupAndRethrowIllegalState();
+            }
+        }
+
+
+        /**
+         * Send a received ECM packet to the specified session of the CA system.
+         *
+         * @param data byte array of the ECM data.
+         * @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.
+         * @throws MediaCasException for CAS-specific errors.
+         * @throws MediaCasStateException for CAS-specific state exceptions.
+         */
+        public void processEcm(@NonNull byte[] data, int offset, int length)
+                throws MediaCasException {
+            validateInternalStates();
+
+            try {
+                mCasData.set(data, offset, length);
+                mICas.processEcm(mSessionId, mCasData);
+            } catch (ServiceSpecificException e) {
+                MediaCasException.throwExceptions(e);
+            } catch (RemoteException e) {
+                cleanupAndRethrowIllegalState();
+            }
+        }
+
+        /**
+         * Send a received ECM packet to the specified session of the CA system.
+         * This is similar to {@link Session#processEcm(byte[], int, int)}
+         * except that the entire byte array is sent.
+         *
+         * @param data byte array of the ECM data.
+         *
+         * @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[] data) throws MediaCasException {
+            processEcm(data, 0, data.length);
+        }
+
+        /**
+         * Close the session.
+         *
+         * @throws IllegalStateException if the MediaCas instance is not valid.
+         * @throws MediaCasStateException for CAS-specific state exceptions.
+         */
+        @Override
+        public void close() {
+            validateInternalStates();
+
+            try {
+                mICas.closeSession(mSessionId);
+            } catch (ServiceSpecificException e) {
+                MediaCasStateException.throwExceptions(e);
+            } catch (RemoteException e) {
+                cleanupAndRethrowIllegalState();
+            }
+        }
+    }
+
+    Session createFromSessionId(byte[] sessionId) {
+        if (sessionId == null || sessionId.length == 0) {
+            return null;
+        }
+        return new Session(sessionId);
+    }
+
+    /**
      * Class for parceling CAS plugin descriptors over IMediaCasService binder.
      */
     static class ParcelableCasPluginDescriptor
@@ -408,17 +510,17 @@ public final class MediaCas {
      *
      * @param programNumber program_number of the program (as in ISO/IEC13818-1).
      *
-     * @return session id of the newly opened session.
+     * @return session the newly opened session.
      *
      * @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) throws MediaCasException {
+    public Session openSession(int programNumber) throws MediaCasException {
         validateInternalStates();
 
         try {
-            return mICas.openSession(programNumber);
+            return createFromSessionId(mICas.openSession(programNumber));
         } catch (ServiceSpecificException e) {
             MediaCasException.throwExceptions(e);
         } catch (RemoteException e) {
@@ -433,18 +535,18 @@ public final class MediaCas {
      * @param programNumber program_number of the stream (as in ISO/IEC13818-1).
      * @param elementaryPID elementary_PID of the stream (as in ISO/IEC13818-1).
      *
-     * @return session id of the newly opened session.
+     * @return session the newly opened session.
      *
      * @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 Session openSession(int programNumber, int elementaryPID)
             throws MediaCasException {
         validateInternalStates();
 
         try {
-            return mICas.openSessionForStream(programNumber, elementaryPID);
+            return createFromSessionId(mICas.openSessionForStream(programNumber, elementaryPID));
         } catch (ServiceSpecificException e) {
             MediaCasException.throwExceptions(e);
         } catch (RemoteException e) {
@@ -454,92 +556,6 @@ public final class MediaCas {
     }
 
     /**
-     * Close the specified session.
-     *
-     * @param sessionId the session to be closed.
-     *
-     * @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();
-        }
-    }
-
-    /**
-     * Set the private data for a session.
-     *
-     * @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.
-     * @throws MediaCasException for CAS-specific errors.
-     * @throws MediaCasStateException for CAS-specific state exceptions.
-     */
-    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();
-        }
-    }
-
-    /**
-     * Send a received ECM packet to the specified session of the CA system.
-     *
-     * @param sessionId the session for which the ECM is intended.
-     * @param data byte array of the ECM data.
-     * @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.
-     * @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) throws MediaCasException {
-        validateInternalStates();
-
-        try {
-            mCasData.set(data, offset, length);
-            mICas.processEcm(sessionId, mCasData);
-        } catch (ServiceSpecificException e) {
-            MediaCasException.throwExceptions(e);
-        } catch (RemoteException e) {
-            cleanupAndRethrowIllegalState();
-        }
-    }
-
-    /**
-     * Send a received ECM packet to the specified session of the CA system.
-     * This is similar to {@link #processEcm(byte[], byte[], int, int)}
-     * except that the entire byte array is sent.
-     *
-     * @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.
-     * @throws MediaCasException for CAS-specific errors.
-     * @throws MediaCasStateException for CAS-specific state exceptions.
-     */
-    public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data)
-            throws MediaCasException {
-        processEcm(sessionId, data, 0, data.length);
-    }
-
-    /**
      * Send a received EMM packet to the CA system.
      *
      * @param data byte array of the EMM data.
@@ -650,10 +666,8 @@ public final class MediaCas {
         }
     }
 
-    /**
-     * Release the MediaCas instance.
-     */
-    public void release() {
+    @Override
+    public void close() {
         if (mICas != null) {
             try {
                 mICas.release();
@@ -666,6 +680,6 @@ public final class MediaCas {
 
     @Override
     protected void finalize() {
-        release();
+        close();
     }
 }
\ No newline at end of file
index 2dd1097..b75b7dd 100644 (file)
@@ -38,7 +38,7 @@ import java.nio.ByteBuffer;
  * Scrambling schemes are identified by 16-bit unsigned integer as in CA_system_id.
  *
  */
-public final class MediaDescrambler {
+public final class MediaDescrambler implements AutoCloseable {
     private static final String TAG = "MediaDescrambler";
     private IDescrambler mIDescrambler;
 
@@ -141,17 +141,17 @@ public final class MediaDescrambler {
      * android.media.MediaCodec#queueSecureInputBuffer} by specifying even
      * or odd key in the {@link android.media.MediaCodec.CryptoInfo#key} field.
      *
-     * @param sessionId the MediaCas sessionId to associate with this
+     * @param session the MediaCas session to associate with this
      * MediaDescrambler instance.
      *
      * @throws IllegalStateException if the descrambler instance is not valid.
      * @throws MediaCasStateException for CAS-specific state exceptions.
      */
-    public final void setMediaCasSession(@NonNull byte[] sessionId) {
+    public final void setMediaCasSession(@NonNull MediaCas.Session session) {
         validateInternalStates();
 
         try {
-            mIDescrambler.setMediaCasSession(sessionId);
+            mIDescrambler.setMediaCasSession(session.mSessionId);
         } catch (ServiceSpecificException e) {
             MediaCasStateException.throwExceptions(e);
         } catch (RemoteException e) {
@@ -163,11 +163,10 @@ public final class MediaDescrambler {
      * Descramble a ByteBuffer of data described by a
      * {@link android.media.MediaCodec.CryptoInfo} structure.
      *
-     * @param srcBuf ByteBuffer containing the scrambled data.
-     * @param srcPos position within src where the scrambled data starts.
-     * @param dstBuf ByteBuffer to descramble into. If null, descrambling will happen
-     * in-place and src will be used as dst.
-     * @param dstPos position within dst to put the descrambled data.
+     * @param srcBuf ByteBuffer containing the scrambled data, which starts at
+     * srcBuf.position().
+     * @param dstBuf ByteBuffer to hold the descrambled data, which starts at
+     * dstBuf.position().
      * @param cryptoInfo a {@link android.media.MediaCodec.CryptoInfo} structure
      * describing the subsamples contained in src.
      *
@@ -178,7 +177,7 @@ public final class MediaDescrambler {
      * @throws MediaCasStateException for CAS-specific state exceptions.
      */
     public final int descramble(
-            @NonNull ByteBuffer srcBuf, int srcPos, ByteBuffer dstBuf, int dstPos,
+            @NonNull ByteBuffer srcBuf, @NonNull ByteBuffer dstBuf,
             @NonNull MediaCodec.CryptoInfo cryptoInfo) {
         validateInternalStates();
 
@@ -208,14 +207,16 @@ public final class MediaDescrambler {
                     cryptoInfo.numSubSamples,
                     cryptoInfo.numBytesOfClearData,
                     cryptoInfo.numBytesOfEncryptedData,
-                    srcBuf, srcPos, dstBuf, dstPos);
+                    srcBuf, srcBuf.position(), srcBuf.limit(),
+                    dstBuf, dstBuf.position(), dstBuf.limit());
         } catch (ServiceSpecificException e) {
             MediaCasStateException.throwExceptions(e);
         }
         return -1;
     }
 
-    public final void release() {
+    @Override
+    public void close() {
         if (mIDescrambler != null) {
             try {
                 mIDescrambler.release();
@@ -229,7 +230,7 @@ public final class MediaDescrambler {
 
     @Override
     protected void finalize() {
-        release();
+        close();
     }
 
     private static native final void native_init();
@@ -237,7 +238,8 @@ public final class MediaDescrambler {
     private native final void native_release();
     private native final int native_descramble(
             byte key, int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
-            @NonNull ByteBuffer srcBuf, int srcOffset, ByteBuffer dstBuf, int dstOffset);
+            @NonNull ByteBuffer srcBuf, int srcOffset, int srcLimit,
+            ByteBuffer dstBuf, int dstOffset, int dstLimit);
 
     static {
         System.loadLibrary("media_jni");
index 2ed6668..a0a6a1e 100644 (file)
@@ -259,11 +259,71 @@ final public class MediaExtractor {
      * @param mediaCas the MediaCas object to use.
      */
     public final void setMediaCas(@NonNull MediaCas mediaCas) {
+        mMediaCas = mediaCas;
         nativeSetMediaCas(mediaCas.getBinder());
     }
 
     private native final void nativeSetMediaCas(@NonNull IBinder casBinder);
 
+    /**
+     * Describes the conditional access system used to scramble a track.
+     */
+    public static final class CasInfo {
+        private final int mSystemId;
+        private final MediaCas.Session mSession;
+
+        CasInfo(int systemId, @Nullable MediaCas.Session session) {
+            mSystemId = systemId;
+            mSession = session;
+        }
+
+        /**
+         * Retrieves the system id of the conditional access system.
+         *
+         * @return CA system id of the CAS used to scramble the track.
+         */
+        public int getSystemId() {
+            return mSystemId;
+        }
+
+        /**
+         * Retrieves the {@link MediaCas.Session} associated with a track. The
+         * session is needed to initialize a descrambler in order to decode the
+         * scrambled track.
+         * <p>
+         * @see MediaDescrambler#setMediaCasSession
+         * <p>
+         * @return a {@link MediaCas.Session} object associated with a track.
+         */
+        public MediaCas.Session getSession() {
+            return mSession;
+        }
+    }
+
+    /**
+     * Retrieves the information about the conditional access system used to scramble
+     * a track.
+     *
+     * @param index of the track.
+     * @return an {@link CasInfo} object describing the conditional access system.
+     */
+    public CasInfo getCasInfo(int index) {
+        Map<String, Object> formatMap = getTrackFormatNative(index);
+        if (formatMap.containsKey(MediaFormat.KEY_CA_SYSTEM_ID)) {
+            int systemId = ((Integer)formatMap.get(MediaFormat.KEY_CA_SYSTEM_ID)).intValue();
+            MediaCas.Session session = null;
+            if (mMediaCas != null && formatMap.containsKey(MediaFormat.KEY_CA_SESSION_ID)) {
+                ByteBuffer buf = (ByteBuffer) formatMap.get(MediaFormat.KEY_CA_SESSION_ID);
+                buf.rewind();
+                final byte[] sessionId = new byte[buf.remaining()];
+                buf.get(sessionId);
+                session = mMediaCas.createFromSessionId(sessionId);
+            }
+            return new CasInfo(systemId, session);
+        }
+        return null;
+    }
+
     @Override
     protected void finalize() {
         native_finalize();
@@ -307,31 +367,6 @@ final public class MediaExtractor {
                     return initDataMap.get(schemeUuid);
                 }
             };
-        } else if (formatMap.containsKey("mime")
-                && "video/mp2ts".equals(formatMap.get("mime"))) {
-            final Map<UUID, DrmInitData.SchemeInitData> initDataMap =
-                    new HashMap<UUID, DrmInitData.SchemeInitData>();
-
-            int numTracks = getTrackCount();
-            for (int i = 0; i < numTracks; ++i) {
-                Map<String, Object> trackFormatMap = getTrackFormatNative(i);
-                if (!trackFormatMap.containsKey("cas")) {
-                    continue;
-                }
-                ByteBuffer buf = (ByteBuffer) trackFormatMap.get("cas");
-                buf.rewind();
-                final byte[] data = new byte[buf.remaining()];
-                buf.get(data);
-                initDataMap.put(new UUID(0, i), new DrmInitData.SchemeInitData("cas", data));
-            }
-            if (initDataMap.isEmpty()) {
-                return null;
-            }
-            return new DrmInitData() {
-                public SchemeInitData get(UUID schemeUuid) {
-                    return initDataMap.get(schemeUuid);
-                }
-            };
         } else {
             int numTracks = getTrackCount();
             for (int i = 0; i < numTracks; ++i) {
@@ -349,8 +384,8 @@ final public class MediaExtractor {
                     }
                 };
             }
-            return null;
         }
+        return null;
     }
 
     /**
@@ -680,5 +715,7 @@ final public class MediaExtractor {
         native_init();
     }
 
+    private MediaCas mMediaCas;
+
     private long mNativeContext;
 }
index e77c00b..ed5f7d8 100644 (file)
@@ -767,6 +767,29 @@ public final class MediaFormat {
      */
     public static final String KEY_TRACK_ID = "track-id";
 
+    /**
+     * A key describing the system id of the conditional access system used to scramble
+     * a media track.
+     * <p>
+     * This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
+     * access system.
+     * <p>
+     * The associated value is an integer.
+     * @hide
+     */
+    public static final String KEY_CA_SYSTEM_ID = "ca-system-id";
+
+    /**
+     * A key describing the {@link MediaCas.Session} object associated with a media track.
+     * <p>
+     * This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
+     * access system.
+     * <p>
+     * The associated value is a ByteBuffer.
+     * @hide
+     */
+    public static final String KEY_CA_SESSION_ID = "ca-session-id";
+
     /* package private */ MediaFormat(Map<String, Object> map) {
         mMap = map;
     }
index f031dbb..85d33b7 100644 (file)
@@ -54,11 +54,10 @@ static void setDescrambler(
 }
 
 static status_t getBufferAndSize(
-        JNIEnv *env, jobject byteBuf, jint offset, size_t length,
+        JNIEnv *env, jobject byteBuf, jint offset, jint limit, size_t length,
         void **outPtr, jbyteArray *outByteArray) {
     void *ptr = env->GetDirectBufferAddress(byteBuf);
 
-    size_t bufSize;
     jbyteArray byteArray = NULL;
 
     ScopedLocalRef<jclass> byteBufClass(env, env->FindClass("java/nio/ByteBuffer"));
@@ -78,13 +77,9 @@ static status_t getBufferAndSize(
 
         jboolean isCopy;
         ptr = env->GetByteArrayElements(byteArray, &isCopy);
-
-        bufSize = (size_t) env->GetArrayLength(byteArray);
-    } else {
-        bufSize = (size_t) env->GetDirectBufferCapacity(byteBuf);
     }
 
-    if (length + offset > bufSize) {
+    if ((jint)length + offset > limit) {
         if (byteArray != NULL) {
             env->ReleaseByteArrayElements(byteArray, (jbyte *)ptr, 0);
         }
@@ -294,7 +289,8 @@ static void throwServiceSpecificException(
 static jint android_media_MediaDescrambler_native_descramble(
         JNIEnv *env, jobject thiz, jbyte key, jint numSubSamples,
         jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj,
-        jobject srcBuf, jint srcOffset, jobject dstBuf, jint dstOffset) {
+        jobject srcBuf, jint srcOffset, jint srcLimit,
+        jobject dstBuf, jint dstOffset, jint dstLimit) {
     sp<JDescrambler> descrambler = getDescrambler(env, thiz);
     if (descrambler == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException", NULL);
@@ -307,7 +303,7 @@ static jint android_media_MediaDescrambler_native_descramble(
             numBytesOfEncryptedDataObj, &subSamples);
     if (totalLength < 0) {
         jniThrowException(env, "java/lang/IllegalArgumentException",
-                "Invalid sub sample info!");
+                "Invalid subsample info!");
         return -1;
     }
 
@@ -315,16 +311,23 @@ static jint android_media_MediaDescrambler_native_descramble(
     void *srcPtr = NULL, *dstPtr = NULL;
     jbyteArray srcArray = NULL, dstArray = NULL;
     status_t err = getBufferAndSize(
-            env, srcBuf, srcOffset, totalLength, &srcPtr, &srcArray);
+            env, srcBuf, srcOffset, srcLimit, totalLength, &srcPtr, &srcArray);
 
     if (err == OK) {
         if (dstBuf == NULL) {
             dstPtr = srcPtr;
         } else {
             err = getBufferAndSize(
-                    env, dstBuf, dstOffset, totalLength, &dstPtr, &dstArray);
+                    env, dstBuf, dstOffset, dstLimit, totalLength, &dstPtr, &dstArray);
         }
     }
+
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                "Invalid buffer offset and/or size for subsamples!");
+        return -1;
+    }
+
     Status status;
     if (err == OK) {
         status = descrambler->descramble(
@@ -394,7 +397,7 @@ static const JNINativeMethod gMethods[] = {
             (void *)android_media_MediaDescrambler_native_init },
     { "native_setup", "(Landroid/os/IBinder;)V",
             (void *)android_media_MediaDescrambler_native_setup },
-    { "native_descramble", "(BI[I[ILjava/nio/ByteBuffer;ILjava/nio/ByteBuffer;I)I",
+    { "native_descramble", "(BI[I[ILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)I",
             (void *)android_media_MediaDescrambler_native_descramble },
 };