summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt39
-rw-r--r--api/system-current.txt39
-rw-r--r--api/test-current.txt39
-rw-r--r--media/java/android/media/MediaPlayer.java1045
-rw-r--r--media/jni/android_media_MediaPlayer.cpp577
5 files changed, 1738 insertions, 1 deletions
diff --git a/api/current.txt b/api/current.txt
index 59f7354a68f9..c7fcc26b8742 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22147,7 +22147,10 @@ package android.media {
method public android.media.BufferingParams getBufferingParams();
method public int getCurrentPosition();
method public android.media.BufferingParams getDefaultBufferingParams();
+ method public android.media.MediaPlayer.DrmInfo getDrmInfo();
+ method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public int getDuration();
+ method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
method public android.os.Bundle getMetrics();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -22161,8 +22164,12 @@ package android.media {
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void prepareAsync() throws java.lang.IllegalStateException;
+ method public void prepareDrm(java.util.UUID, android.media.MediaPlayer.OnDrmConfigCallback) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
method public void release();
+ method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
method public void reset();
+ method public void restoreKeys(byte[]) throws android.media.MediaPlayer.NoDrmSchemeException;
method public void seekTo(int, int) throws java.lang.IllegalStateException;
method public void seekTo(int) throws java.lang.IllegalStateException;
method public void selectTrack(int) throws java.lang.IllegalStateException;
@@ -22179,10 +22186,15 @@ package android.media {
method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDisplay(android.view.SurfaceHolder);
+ method public void setDrmPropertyString(java.lang.String, java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public void setLooping(boolean);
method public void setNextMediaPlayer(android.media.MediaPlayer);
method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener);
method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener);
+ method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener);
+ method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler);
+ method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener);
+ method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener, android.os.Handler);
method public void setOnErrorListener(android.media.MediaPlayer.OnErrorListener);
method public void setOnInfoListener(android.media.MediaPlayer.OnInfoListener);
method public void setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener);
@@ -22225,6 +22237,16 @@ package android.media {
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
}
+ public static final class MediaPlayer.DrmInfo {
+ method public java.lang.String[] getMimes();
+ method public java.util.Map<java.util.UUID, byte[]> getPssh();
+ method public java.util.UUID[] getSupportedSchemes();
+ }
+
+ public static final class MediaPlayer.NoDrmSchemeException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.NoDrmSchemeException(java.lang.String);
+ }
+
public static abstract interface MediaPlayer.OnBufferingUpdateListener {
method public abstract void onBufferingUpdate(android.media.MediaPlayer, int);
}
@@ -22233,6 +22255,19 @@ package android.media {
method public abstract void onCompletion(android.media.MediaPlayer);
}
+ public static abstract class MediaPlayer.OnDrmConfigCallback {
+ ctor public MediaPlayer.OnDrmConfigCallback();
+ method public void onDrmConfig(android.media.MediaPlayer);
+ }
+
+ public static abstract interface MediaPlayer.OnDrmInfoListener {
+ method public abstract void onDrmInfo(android.media.MediaPlayer, android.media.MediaPlayer.DrmInfo);
+ }
+
+ public static abstract interface MediaPlayer.OnDrmPreparedListener {
+ method public abstract void onDrmPrepared(android.media.MediaPlayer, boolean);
+ }
+
public static abstract interface MediaPlayer.OnErrorListener {
method public abstract boolean onError(android.media.MediaPlayer, int, int);
}
@@ -22261,6 +22296,10 @@ package android.media {
method public abstract void onVideoSizeChanged(android.media.MediaPlayer, int, int);
}
+ public static final class MediaPlayer.ProvisioningErrorException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.ProvisioningErrorException(java.lang.String);
+ }
+
public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
method public int describeContents();
method public android.media.MediaFormat getFormat();
diff --git a/api/system-current.txt b/api/system-current.txt
index 3b1811eb387d..02432f00e8b8 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -23751,7 +23751,10 @@ package android.media {
method public android.media.BufferingParams getBufferingParams();
method public int getCurrentPosition();
method public android.media.BufferingParams getDefaultBufferingParams();
+ method public android.media.MediaPlayer.DrmInfo getDrmInfo();
+ method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public int getDuration();
+ method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
method public android.os.Bundle getMetrics();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -23765,8 +23768,12 @@ package android.media {
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void prepareAsync() throws java.lang.IllegalStateException;
+ method public void prepareDrm(java.util.UUID, android.media.MediaPlayer.OnDrmConfigCallback) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
method public void release();
+ method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
method public void reset();
+ method public void restoreKeys(byte[]) throws android.media.MediaPlayer.NoDrmSchemeException;
method public void seekTo(int, int) throws java.lang.IllegalStateException;
method public void seekTo(int) throws java.lang.IllegalStateException;
method public void selectTrack(int) throws java.lang.IllegalStateException;
@@ -23783,10 +23790,15 @@ package android.media {
method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDisplay(android.view.SurfaceHolder);
+ method public void setDrmPropertyString(java.lang.String, java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public void setLooping(boolean);
method public void setNextMediaPlayer(android.media.MediaPlayer);
method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener);
method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener);
+ method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener);
+ method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler);
+ method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener);
+ method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener, android.os.Handler);
method public void setOnErrorListener(android.media.MediaPlayer.OnErrorListener);
method public void setOnInfoListener(android.media.MediaPlayer.OnInfoListener);
method public void setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener);
@@ -23829,6 +23841,16 @@ package android.media {
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
}
+ public static final class MediaPlayer.DrmInfo {
+ method public java.lang.String[] getMimes();
+ method public java.util.Map<java.util.UUID, byte[]> getPssh();
+ method public java.util.UUID[] getSupportedSchemes();
+ }
+
+ public static final class MediaPlayer.NoDrmSchemeException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.NoDrmSchemeException(java.lang.String);
+ }
+
public static abstract interface MediaPlayer.OnBufferingUpdateListener {
method public abstract void onBufferingUpdate(android.media.MediaPlayer, int);
}
@@ -23837,6 +23859,19 @@ package android.media {
method public abstract void onCompletion(android.media.MediaPlayer);
}
+ public static abstract class MediaPlayer.OnDrmConfigCallback {
+ ctor public MediaPlayer.OnDrmConfigCallback();
+ method public void onDrmConfig(android.media.MediaPlayer);
+ }
+
+ public static abstract interface MediaPlayer.OnDrmInfoListener {
+ method public abstract void onDrmInfo(android.media.MediaPlayer, android.media.MediaPlayer.DrmInfo);
+ }
+
+ public static abstract interface MediaPlayer.OnDrmPreparedListener {
+ method public abstract void onDrmPrepared(android.media.MediaPlayer, boolean);
+ }
+
public static abstract interface MediaPlayer.OnErrorListener {
method public abstract boolean onError(android.media.MediaPlayer, int, int);
}
@@ -23865,6 +23900,10 @@ package android.media {
method public abstract void onVideoSizeChanged(android.media.MediaPlayer, int, int);
}
+ public static final class MediaPlayer.ProvisioningErrorException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.ProvisioningErrorException(java.lang.String);
+ }
+
public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
method public int describeContents();
method public android.media.MediaFormat getFormat();
diff --git a/api/test-current.txt b/api/test-current.txt
index 6df2ab165c64..b599c1c274b5 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -22239,7 +22239,10 @@ package android.media {
method public android.media.BufferingParams getBufferingParams();
method public int getCurrentPosition();
method public android.media.BufferingParams getDefaultBufferingParams();
+ method public android.media.MediaPlayer.DrmInfo getDrmInfo();
+ method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public int getDuration();
+ method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
method public android.os.Bundle getMetrics();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -22253,8 +22256,12 @@ package android.media {
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void prepareAsync() throws java.lang.IllegalStateException;
+ method public void prepareDrm(java.util.UUID, android.media.MediaPlayer.OnDrmConfigCallback) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
method public void release();
+ method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
method public void reset();
+ method public void restoreKeys(byte[]) throws android.media.MediaPlayer.NoDrmSchemeException;
method public void seekTo(int, int) throws java.lang.IllegalStateException;
method public void seekTo(int) throws java.lang.IllegalStateException;
method public void selectTrack(int) throws java.lang.IllegalStateException;
@@ -22271,10 +22278,15 @@ package android.media {
method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDisplay(android.view.SurfaceHolder);
+ method public void setDrmPropertyString(java.lang.String, java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public void setLooping(boolean);
method public void setNextMediaPlayer(android.media.MediaPlayer);
method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener);
method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener);
+ method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener);
+ method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler);
+ method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener);
+ method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener, android.os.Handler);
method public void setOnErrorListener(android.media.MediaPlayer.OnErrorListener);
method public void setOnInfoListener(android.media.MediaPlayer.OnInfoListener);
method public void setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener);
@@ -22317,6 +22329,16 @@ package android.media {
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
}
+ public static final class MediaPlayer.DrmInfo {
+ method public java.lang.String[] getMimes();
+ method public java.util.Map<java.util.UUID, byte[]> getPssh();
+ method public java.util.UUID[] getSupportedSchemes();
+ }
+
+ public static final class MediaPlayer.NoDrmSchemeException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.NoDrmSchemeException(java.lang.String);
+ }
+
public static abstract interface MediaPlayer.OnBufferingUpdateListener {
method public abstract void onBufferingUpdate(android.media.MediaPlayer, int);
}
@@ -22325,6 +22347,19 @@ package android.media {
method public abstract void onCompletion(android.media.MediaPlayer);
}
+ public static abstract class MediaPlayer.OnDrmConfigCallback {
+ ctor public MediaPlayer.OnDrmConfigCallback();
+ method public void onDrmConfig(android.media.MediaPlayer);
+ }
+
+ public static abstract interface MediaPlayer.OnDrmInfoListener {
+ method public abstract void onDrmInfo(android.media.MediaPlayer, android.media.MediaPlayer.DrmInfo);
+ }
+
+ public static abstract interface MediaPlayer.OnDrmPreparedListener {
+ method public abstract void onDrmPrepared(android.media.MediaPlayer, boolean);
+ }
+
public static abstract interface MediaPlayer.OnErrorListener {
method public abstract boolean onError(android.media.MediaPlayer, int, int);
}
@@ -22353,6 +22388,10 @@ package android.media {
method public abstract void onVideoSizeChanged(android.media.MediaPlayer, int, int);
}
+ public static final class MediaPlayer.ProvisioningErrorException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.ProvisioningErrorException(java.lang.String);
+ }
+
public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
method public int describeContents();
method public android.media.MediaFormat getFormat();
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 88dde53eff46..62abdefde119 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -47,6 +47,7 @@ import android.widget.VideoView;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.media.BufferingParams;
+import android.media.MediaDrm;
import android.media.MediaFormat;
import android.media.MediaTimeProvider;
import android.media.PlaybackParams;
@@ -60,6 +61,7 @@ import com.android.internal.util.Preconditions;
import libcore.io.IoBridge;
import libcore.io.Libcore;
+import libcore.io.Streams;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -70,13 +72,20 @@ import java.io.InputStream;
import java.lang.Runnable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
+import java.net.URL;
+import java.nio.ByteOrder;
+import java.util.Arrays;
import java.util.BitSet;
+import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
+import java.util.UUID;
import java.util.Vector;
-import java.lang.ref.WeakReference;
+
/**
* MediaPlayer class can be used to control playback
@@ -625,6 +634,17 @@ public class MediaPlayer extends PlayerBase
private int mUsage = -1;
private boolean mBypassInterruptionPolicy;
+ // Modular DRM
+ private UUID mDrmUUID;
+ private final Object mDrmLock = new Object();
+ private DrmInfo mDrmInfo;
+ private boolean mDrmInfoResolved;
+ private boolean mActiveDrmScheme;
+ private boolean mDrmConfigAllowed;
+ private boolean mDrmProvisioningInProgress;
+ private boolean mPrepareDrmInProgress;
+ private ProvisioningThread mDrmProvisioningThread;
+
/**
* Default constructor. Consider using one of the create() methods for
* synchronously instantiating a MediaPlayer from a Uri or resource.
@@ -1866,6 +1886,12 @@ public class MediaPlayer extends PlayerBase
mTimeProvider = null;
}
mOnSubtitleDataListener = null;
+
+ // Modular DRM clean up
+ mOnDrmInfoHandlerDelegate = null;
+ mOnDrmPreparedHandlerDelegate = null;
+ resetDrmState();
+
_release();
}
@@ -1906,6 +1932,8 @@ public class MediaPlayer extends PlayerBase
mIndexTrackPairs.clear();
mInbandTrackIndices.clear();
};
+
+ resetDrmState();
}
private native void _reset();
@@ -2999,6 +3027,7 @@ public class MediaPlayer extends PlayerBase
private static final int MEDIA_INFO = 200;
private static final int MEDIA_SUBTITLE_DATA = 201;
private static final int MEDIA_META_DATA = 202;
+ private static final int MEDIA_DRM_INFO = 210;
private TimeProvider mTimeProvider;
@@ -3037,11 +3066,43 @@ public class MediaPlayer extends PlayerBase
MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
sendMessage(msg2);
}
+
+ // MEDIA_DRM_INFO is fired (if available) before MEDIA_PREPARED.
+ // An empty mDrmInfo indicates prepared is done but the source is not DRM protected.
+ // Setting this before the callback so onPreparedListener can call getDrmInfo to
+ // get the right state
+ mDrmInfoResolved = true;
+
OnPreparedListener onPreparedListener = mOnPreparedListener;
if (onPreparedListener != null)
onPreparedListener.onPrepared(mMediaPlayer);
return;
+ case MEDIA_DRM_INFO:
+ Log.v(TAG, "MEDIA_DRM_INFO " + mOnDrmInfoHandlerDelegate);
+
+ if (msg.obj == null) {
+ Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL");
+ } else if (msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel)msg.obj;
+ DrmInfo drmInfo = new DrmInfo(parcel);
+
+ OnDrmInfoHandlerDelegate onDrmInfoHandlerDelegate;
+ synchronized (mDrmLock) {
+ mDrmInfo = drmInfo.makeCopy();
+ // local copy while keeping the lock
+ onDrmInfoHandlerDelegate = mOnDrmInfoHandlerDelegate;
+ }
+
+ // notifying the client outside the lock
+ if (onDrmInfoHandlerDelegate != null) {
+ onDrmInfoHandlerDelegate.notifyClient(drmInfo);
+ }
+ } else {
+ Log.w(TAG, "MEDIA_DRM_INFO msg.obj NONE; UNEXPECTED" + msg.obj);
+ }
+ return;
+
case MEDIA_PLAYBACK_COMPLETE:
{
mOnCompletionInternalListener.onCompletion(mMediaPlayer);
@@ -3700,6 +3761,988 @@ public class MediaPlayer extends PlayerBase
private OnInfoListener mOnInfoListener;
+ // Modular DRM begin
+
+ /**
+ * Interface definition of a callback to be invoked when the app
+ * can do DRM configuration (get/set properties) before the session
+ * is opened. This facilitates configuration of the properties, like
+ * 'securityLevel', which has to be set after DRM scheme creation but
+ * before the DRM session is opened.
+ *
+ * The only allowed DRM calls in this listener are getDrmPropertyString
+ * and setDrmPropertyString.
+ *
+ */
+ public static abstract class OnDrmConfigCallback
+ {
+ /**
+ * Called to give the app the opportunity to configure DRM before the session is created
+ *
+ * @param mp the {@code MediaPlayer} associated with this callback
+ */
+ public void onDrmConfig(MediaPlayer mp) {}
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the
+ * DRM info becomes available
+ */
+ public interface OnDrmInfoListener
+ {
+ /**
+ * Called to indicate DRM info is available
+ *
+ * @param mp the {@code MediaPlayer} associated with this callback
+ * @param drmInfo DRM info of the source including PSSH, mimes, and subset
+ * of crypto schemes supported by this device
+ */
+ public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo);
+ }
+
+ /**
+ * Register a callback to be invoked when the DRM info is
+ * known.
+ *
+ * @param listener the callback that will be run
+ */
+ public void setOnDrmInfoListener(OnDrmInfoListener listener)
+ {
+ setOnDrmInfoListener(listener, null);
+ }
+
+ /**
+ * Register a callback to be invoked when the DRM info is
+ * known.
+ *
+ * @param listener the callback that will be run
+ */
+ public void setOnDrmInfoListener(OnDrmInfoListener listener, Handler handler)
+ {
+ synchronized (mDrmLock) {
+ if (listener != null) {
+ mOnDrmInfoHandlerDelegate = new OnDrmInfoHandlerDelegate(this, listener, handler);
+ } else {
+ mOnDrmInfoHandlerDelegate = null;
+ }
+ } // synchronized
+ }
+
+ private OnDrmInfoHandlerDelegate mOnDrmInfoHandlerDelegate;
+
+ /**
+ * Interface definition of a callback to notify the app when the
+ * DRM is ready for key request/response
+ */
+ public interface OnDrmPreparedListener
+ {
+ /**
+ * Called to notify the app that prepareDrm is finished and ready for key request/response
+ *
+ * @param mp the {@code MediaPlayer} associated with this callback
+ * @param success the result of DRM preparation
+ */
+ public void onDrmPrepared(MediaPlayer mp, boolean success);
+ }
+
+ /**
+ * Register a callback to be invoked when the DRM object is prepared.
+ *
+ * @param listener the callback that will be run
+ */
+ public void setOnDrmPreparedListener(OnDrmPreparedListener listener)
+ {
+ setOnDrmPreparedListener(listener, null);
+ }
+
+ /**
+ * Register a callback to be invoked when the DRM object is prepared.
+ *
+ * @param listener the callback that will be run
+ * @param handler the Handler that will receive the callback
+ */
+ public void setOnDrmPreparedListener(OnDrmPreparedListener listener, Handler handler)
+ {
+ synchronized (mDrmLock) {
+ if (listener != null) {
+ mOnDrmPreparedHandlerDelegate = new OnDrmPreparedHandlerDelegate(this,
+ listener, handler);
+ } else {
+ mOnDrmPreparedHandlerDelegate = null;
+ }
+ } // synchronized
+ }
+
+ private OnDrmPreparedHandlerDelegate mOnDrmPreparedHandlerDelegate;
+
+
+ private class OnDrmInfoHandlerDelegate {
+ private MediaPlayer mMediaPlayer;
+ private OnDrmInfoListener mOnDrmInfoListener;
+ private Handler mHandler;
+
+ OnDrmInfoHandlerDelegate(MediaPlayer mp, OnDrmInfoListener listener, Handler handler) {
+ mMediaPlayer = mp;
+ mOnDrmInfoListener = listener;
+
+ // find the looper for our new event handler
+ Looper looper = null;
+ if (handler != null) {
+ looper = handler.getLooper();
+ }
+
+ // construct the event handler with this looper
+ if (looper != null) {
+ // implement the event handler delegate
+ mHandler = new Handler(looper) {
+ public void handleMessage(Message msg) {
+ DrmInfo drmInfo = (DrmInfo)msg.obj;
+ mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo);
+ }
+ };
+ }
+ }
+
+ void notifyClient(DrmInfo drmInfo) {
+ if ( mHandler != null ) {
+ Message msg = new Message(); // no message type needed
+ msg.obj = drmInfo;
+ mHandler.sendMessage(msg);
+ }
+ else { // no handler: direct call
+ mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo);
+ }
+ }
+ }
+
+ private class OnDrmPreparedHandlerDelegate {
+ private MediaPlayer mMediaPlayer;
+ private OnDrmPreparedListener mOnDrmPreparedListener;
+ private Handler mHandler;
+
+ OnDrmPreparedHandlerDelegate(MediaPlayer mp, OnDrmPreparedListener listener,
+ Handler handler) {
+ mMediaPlayer = mp;
+ mOnDrmPreparedListener = listener;
+
+ // find the looper for our new event handler
+ Looper looper = null;
+ if (handler != null) {
+ looper = handler.getLooper();
+ }
+
+ // construct the event handler with this looper
+ if (looper != null) {
+ // implement the event handler delegate
+ mHandler = new Handler(looper) {
+ public void handleMessage(Message msg) {
+ boolean success = (msg.arg1 == 0) ? false : true;
+ mOnDrmPreparedListener.onDrmPrepared(mMediaPlayer, success);
+ }
+ };
+ }
+ }
+
+ void notifyClient(boolean success) {
+ if ( mHandler != null ) {
+ Message msg = new Message(); // no message type needed
+ msg.arg1 = success ? 1 : 0;
+ mHandler.sendMessage(msg);
+ }
+ else { // no handler: direct call
+ mOnDrmPreparedListener.onDrmPrepared(mMediaPlayer, success);
+ }
+ }
+ }
+
+ /**
+ * Retrieves the DRM Info associated with the current source
+ *
+ * @throws IllegalStateException if called before prepare()
+ */
+ public DrmInfo getDrmInfo()
+ {
+ DrmInfo drmInfo = null;
+
+ // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet;
+ // regardless below returns drmInfo anyway instead of raising an exception
+ synchronized (mDrmLock) {
+ if (!mDrmInfoResolved && mDrmInfo == null) {
+ final String msg = "The Player has not been prepared yet";
+ Log.v(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mDrmInfo != null) {
+ drmInfo = mDrmInfo.makeCopy();
+ }
+ } // synchronized
+
+ return drmInfo;
+ }
+
+ private native void _prepareDrm(@NonNull byte[] uuid, int mode)
+ throws UnsupportedSchemeException, ResourceBusyException, NotProvisionedException;
+
+ /**
+ * Prepares the DRM for the current source
+ * <p>
+ * If {@code OnDrmConfigCallback} is registered, it will be called half-way into
+ * preparation to allow configuration of the DRM properties before opening the
+ * DRM session. Note that the callback is called synchronously in the thread that called
+ * {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString}
+ * and {@code setDrmPropertyString} calls and refrain from any lengthy operation.
+ * <p>
+ * If the device has not been provisioned before, this call also provisions the device
+ * which involves accessing the provisioning server and can take a variable time to
+ * complete depending on the network connectivity.
+ * If OnDrmPreparedListener is registered, prepareDrm() runs in non-blocking
+ * mode by launching the provisioning in the background and returning. The listener
+ * will be called when provisioning and preperation has finished. If a
+ * OnDrmPreparedListener is not registered, prepareDrm() waits till provisioning
+ * and preperation has finished, i.e., runs in blocking mode.
+ * <p>
+ * If OnDrmPreparedListener is registered, it is called to indicated the DRM session
+ * being ready regardless of blocking or non-blocking mode. The application should
+ * not make any assumption about its call sequence (e.g., before or after prepareDrm
+ * returns) or the thread context that will execute the listener.
+ * <p>
+ *
+ * @param uuid The UUID of the crypto scheme.
+ *
+ * @throws IllegalStateException if called before prepare(), or there exists a Drm already
+ * @throws UnsupportedSchemeException if the crypto scheme is not supported
+ * @throws ResourceBusyException if required DRM resources are in use
+ * @throws ProvisioningErrorException if provisioning is required but an attempt failed
+ */
+ public void prepareDrm(@NonNull UUID uuid, OnDrmConfigCallback configCallback)
+ throws UnsupportedSchemeException,
+ ResourceBusyException, ProvisioningErrorException
+ {
+ boolean allDoneWithoutProvisioning = false;
+ // get a snapshot as we'll use them outside the lock
+ OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate = null;
+
+ synchronized (mDrmLock) {
+
+ // only allowing if tied to a protected source; might releax for releasing offline keys
+ if (mDrmInfo == null) {
+ final String msg = String.format("prepareDrm(%s): Wrong usage: " +
+ "The player must be prepared and DRM " +
+ "info be retrieved before this call.", uuid);
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mActiveDrmScheme) {
+ final String msg = String.format("prepareDrm(%s): Wrong usage: There is already " +
+ "an active DRM scheme with %s.", uuid, mDrmUUID);
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mPrepareDrmInProgress) {
+ final String msg = String.format("prepareDrm(%s): Wrong usage: There is already " +
+ "a pending prepareDrm call.", uuid);
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mDrmProvisioningInProgress) {
+ final String msg = String.format("prepareDrm(%s): Unexpectd: Provisioning is " +
+ "already in progress.", uuid);
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ mPrepareDrmInProgress = true;
+ // local copy while the lock is held
+ onDrmPreparedHandlerDelegate = mOnDrmPreparedHandlerDelegate;
+
+ if (configCallback != null) {
+ try {
+ boolean allowOpenSession = false; // just pre-openSession
+ _prepareDrm(getByteArrayFromUUID(uuid), allowOpenSession ? 1 : 0);
+ } catch (IllegalStateException e) {
+ final String msg = String.format("prepareDrm(): Wrong usage: The player must " +
+ "be in prepared state to call prepareDrm().");
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ } catch (NotProvisionedException e) { // the pre-config step won't raise this
+ final String msg = String.format("prepareDrm: Unexpected " +
+ "NotProvisionedException here.");
+ Log.e(TAG, msg);
+ throw new ProvisioningErrorException(msg);
+ } catch (Exception e) {
+ Log.w(TAG, String.format("prepareDrm: Exception %s", e));
+ throw e;
+ } finally {
+ mPrepareDrmInProgress = false;
+ }
+ }
+
+ mDrmConfigAllowed = true;
+ } // synchronized
+
+
+ // call the callback outside the lock
+ if (configCallback != null) {
+ configCallback.onDrmConfig(this);
+ }
+
+ synchronized (mDrmLock) {
+ mDrmConfigAllowed = false;
+
+ try {
+ boolean allowOpenSession = true; // all in
+ _prepareDrm(getByteArrayFromUUID(uuid), allowOpenSession ? 1 : 0);
+
+ mDrmUUID = uuid;
+ mActiveDrmScheme = true;
+
+ mPrepareDrmInProgress = false;
+
+ allDoneWithoutProvisioning = true;
+ } catch (IllegalStateException e) {
+ final String msg = String.format("prepareDrm(%s): Wrong usage: The player must be" +
+ " in prepared state to call prepareDrm().", uuid);
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, String.format("prepareDrm: NotProvisionedException"));
+
+ // handle provisioning internally
+ boolean result = HandleProvisioninig(uuid);
+
+ // if blocking mode, we're already done;
+ // if non-blocking mode, we attempted to launch background provisioning
+ if (result == false) {
+ final String msg =
+ String.format("prepareDrm: Provisioning was required but failed.");
+ Log.e(TAG, msg);
+ throw new ProvisioningErrorException(msg);
+ }
+
+ // nothing else to do;
+ // if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup
+ } catch (Exception e) {
+ Log.w(TAG, String.format("prepareDrm: Exception %s", e));
+ throw e;
+ } finally {
+ mPrepareDrmInProgress = false;
+ }
+ } // synchronized
+
+
+ // if finished successfully without provisioning, call the callback outside the lock
+ if (allDoneWithoutProvisioning) {
+ if (onDrmPreparedHandlerDelegate != null)
+ onDrmPreparedHandlerDelegate.notifyClient(true /*success*/);
+ }
+
+ }
+
+
+ private native void _releaseDrm();
+
+ /**
+ * Releases the DRM session
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session to release
+ */
+ public void releaseDrm()
+ throws NoDrmSchemeException
+ {
+ synchronized (mDrmLock) {
+ if (!mActiveDrmScheme) {
+ Log.e(TAG, String.format("releaseDrm(%s): No active DRM scheme to release."));
+ throw new NoDrmSchemeException("releaseDrm: No active DRM scheme to release.");
+ } else {
+ _releaseDrm();
+
+ mActiveDrmScheme = false;
+ }
+ } // synchronized
+ }
+
+
+ @NonNull
+ private native MediaDrm.KeyRequest _getKeyRequest(@NonNull byte[] scope,
+ @Nullable String mimeType, @MediaDrm.KeyType int keyType,
+ @Nullable Map<String, String> optionalParameters)
+ throws NotProvisionedException;
+
+ /**
+ * A key request/response exchange occurs between the app and a license server
+ * to obtain or release keys used to decrypt encrypted content.
+ * <p>
+ * getKeyRequest() is used to obtain an opaque key request byte array that is
+ * delivered to the license server. The opaque key request byte array is returned
+ * in KeyRequest.data. The recommended URL to deliver the key request to is
+ * returned in KeyRequest.defaultUrl.
+ * <p>
+ * After the app has received the key request response from the server,
+ * it should deliver to the response to the DRM engine plugin using the method
+ * {@link #provideKeyResponse}.
+ *
+ * @param scope may be a container-specific initialization data or a keySetId,
+ * depending on the specified keyType.
+ * When the keyType is KEY_TYPE_STREAMING or KEY_TYPE_OFFLINE, scope should be set to
+ * the container-specific initialization data. Its meaning is interpreted based on the
+ * mime type provided in the mimeType parameter. It could contain, for example,
+ * the content ID, key ID or other data obtained from the content metadata that is
+ * required in generating the key request.
+ * When the keyType is KEY_TYPE_RELEASE, scope should be set to the keySetId of
+ * the keys being released.
+ *
+ * @param mimeType identifies the mime type of the content
+ *
+ * @param keyType specifes the type of the request. The request may be to acquire
+ * keys for streaming or offline content, or to release previously acquired
+ * keys, which are identified by a keySetId.
+ *
+ * @param optionalParameters are included in the key request message to
+ * allow a client application to provide additional message parameters to the server.
+ * This may be {@code null} if no additional parameters are to be sent.
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session
+ */
+ @NonNull
+ public MediaDrm.KeyRequest getKeyRequest(@NonNull byte[] scope, @Nullable String mimeType,
+ @MediaDrm.KeyType int keyType, @Nullable Map<String, String> optionalParameters)
+ throws NoDrmSchemeException
+ {
+ synchronized (mDrmLock) {
+ if (!mActiveDrmScheme) {
+ Log.e(TAG, String.format("getKeyRequest NoDrmSchemeException"));
+ throw new NoDrmSchemeException("getKeyRequest: Has to set a DRM scheme first.");
+ }
+
+ try {
+ return _getKeyRequest(scope, mimeType, keyType, optionalParameters);
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, String.format("getKeyRequest NotProvisionedException: " +
+ "Unexpected. Shouldn't have reached here."));
+ throw new IllegalStateException("getKeyRequest: Unexpected provisioning error.");
+ } catch (Exception e) {
+ Log.w(TAG, String.format("getKeyRequest Exception %s", e));
+ throw e;
+ }
+
+ } // synchronized
+ }
+
+
+ @Nullable
+ private native byte[] _provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response)
+ throws DeniedByServerException;
+
+ /**
+ * A key response is received from the license server by the app, then it is
+ * provided to the DRM engine plugin using provideKeyResponse. When the
+ * response is for an offline key request, a key-set identifier is returned that
+ * can be used to later restore the keys to a new session with the method
+ * {@ link # restoreKeys}.
+ * When the response is for a streaming or release request, null is returned.
+ *
+ * @param keySetId When the response is for a release request, keySetId identifies
+ * the saved key associated with the release request (i.e., the same keySetId
+ * passed to the earlier {@ link # getKeyRequest} call. It MUST be null when the
+ * response is for either streaming or offline key requests.
+ *
+ * @param response the byte array response from the server
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session
+ * @throws DeniedByServerException if the response indicates that the
+ * server rejected the request
+ */
+ public byte[] provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response)
+ throws NoDrmSchemeException, DeniedByServerException
+ {
+ synchronized (mDrmLock) {
+
+ if (!mActiveDrmScheme) {
+ Log.e(TAG, String.format("getKeyRequest NoDrmSchemeException"));
+ throw new NoDrmSchemeException("getKeyRequest: Has to set a DRM scheme first.");
+ }
+
+ try {
+ return _provideKeyResponse(keySetId, response);
+ } catch (Exception e) {
+ Log.w(TAG, String.format("provideKeyResponse Exception %s", e));
+ throw e;
+ }
+ } // synchronized
+ }
+
+
+ private native void _restoreKeys(@NonNull byte[] keySetId);
+
+ /**
+ * Restore persisted offline keys into a new session. keySetId identifies the
+ * keys to load, obtained from a prior call to {@link #provideKeyResponse}.
+ *
+ * @param keySetId identifies the saved key set to restore
+ */
+ public void restoreKeys(@NonNull byte[] keySetId)
+ throws NoDrmSchemeException
+ {
+ synchronized (mDrmLock) {
+
+ if (!mActiveDrmScheme) {
+ Log.w(TAG, String.format("restoreKeys NoDrmSchemeException"));
+ throw new NoDrmSchemeException("restoreKeys: Has to set a DRM scheme first.");
+ }
+
+ try {
+ _restoreKeys(keySetId);
+ } catch (Exception e) {
+ Log.w(TAG, String.format("restoreKeys Exception %s", e));
+ throw e;
+ }
+
+ } // synchronized
+ }
+
+
+ @NonNull
+ private native String _getDrmPropertyString(@NonNull String propertyName);
+
+ /**
+ * Read a DRM engine plugin String property value, given the property name string.
+ * <p>
+ * @param propertyName the property name
+ *
+ * Standard fields names are:
+ * {link #PROPERTY_VENDOR}, {link #PROPERTY_VERSION},
+ * {link #PROPERTY_DESCRIPTION}, {link #PROPERTY_ALGORITHMS}
+ */
+ @NonNull
+ public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName)
+ throws NoDrmSchemeException
+ {
+ String value;
+ synchronized (mDrmLock) {
+
+ if (!mActiveDrmScheme && !mDrmConfigAllowed) {
+ Log.w(TAG, String.format("getDrmPropertyString NoDrmSchemeException"));
+ throw new NoDrmSchemeException("getDrmPropertyString: Has to prepareDrm() first.");
+ }
+
+ try {
+ value = _getDrmPropertyString(propertyName);
+ } catch (Exception e) {
+ Log.w(TAG, String.format("getDrmPropertyString Exception %s", e));
+ throw e;
+ }
+ } // synchronized
+
+ return value;
+ }
+
+ private native void _setDrmPropertyString(@NonNull String propertyName, @NonNull String value);
+
+ /**
+ * Set a DRM engine plugin String property value.
+ * <p>
+ * @param propertyName the property name
+ * @param value the property value
+ *
+ * Standard fields names are:
+ * {link #PROPERTY_VENDOR}, {link #PROPERTY_VERSION},
+ * {link #PROPERTY_DESCRIPTION}, {link #PROPERTY_ALGORITHMS}
+ */
+ public void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName,
+ @NonNull String value)
+ throws NoDrmSchemeException
+ {
+ synchronized (mDrmLock) {
+
+ if ( !mActiveDrmScheme && !mDrmConfigAllowed ) {
+ Log.w(TAG, String.format("setDrmPropertyString NoDrmSchemeException"));
+ throw new NoDrmSchemeException("setDrmPropertyString: Has to prepareDrm() first.");
+ }
+
+ try {
+ _setDrmPropertyString(propertyName, value);
+ } catch ( Exception e ) {
+ Log.w(TAG, String.format("setDrmPropertyString Exception %s", e));
+ throw e;
+ }
+ } // synchronized
+ }
+
+ public static final class DrmInfo {
+ private Map<UUID, byte[]> mapPssh;
+ private UUID[] supportedSchemes;
+ // TODO: Won't need this in final release. Only keeping it for the existing test app.
+ private String[] mimes;
+
+ public Map<UUID, byte[]> getPssh() {
+ return mapPssh;
+ }
+ public UUID[] getSupportedSchemes() {
+ return supportedSchemes;
+ }
+ // TODO: Won't need this in final release. Only keeping it for the existing test app.
+ public String[] getMimes() {
+ return mimes;
+ }
+
+ private DrmInfo(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes, String[] Mimes) {
+ mapPssh = Pssh;
+ supportedSchemes = SupportedSchemes;
+ mimes = Mimes;
+ }
+
+ private DrmInfo(Parcel parcel) {
+ Log.v(TAG, "DrmInfo(" + parcel + ") size " + parcel.dataSize());
+
+ int psshsize = parcel.readInt();
+ byte[] pssh = new byte[psshsize];
+ parcel.readByteArray(pssh);
+
+ Log.v(TAG, "DrmInfo() PSSH: " + arrToHex(pssh));
+ mapPssh = parsePSSH(pssh, psshsize);
+ Log.v(TAG, "DrmInfo() PSSH: " + mapPssh);
+
+ int supportedDRMsCount = parcel.readInt();
+ supportedSchemes = new UUID[supportedDRMsCount];
+ for (int i = 0; i < supportedDRMsCount; i++) {
+ byte[] uuid = new byte[16];
+ parcel.readByteArray(uuid);
+
+ supportedSchemes[i] = bytesToUUID(uuid);
+
+ Log.v(TAG, "DrmInfo() supportedScheme[" + i + "]: " +
+ supportedSchemes[i]);
+ }
+
+ // TODO: Won't need this in final release. Only keeping it for the test app.
+ mimes = parcel.readStringArray();
+ int mimeCount = mimes.length;
+ Log.v(TAG, "DrmInfo() mime: " + Arrays.toString(mimes));
+
+ Log.v(TAG, "DrmInfo() Parcel psshsize: " + psshsize +
+ " supportedDRMsCount: " + supportedDRMsCount +
+ " mimeCount: " + mimeCount);
+ }
+
+ private DrmInfo makeCopy() {
+ return new DrmInfo(this.mapPssh, this.supportedSchemes, this.mimes);
+ }
+
+ private String arrToHex(byte[] bytes) {
+ String out = "0x";
+ for (int i = 0; i < bytes.length; i++) {
+ out += String.format("%02x", bytes[i]);
+ }
+
+ return out;
+ }
+
+ private UUID bytesToUUID(byte[] uuid) {
+ long msb = 0, lsb = 0;
+ for (int i = 0; i < 8; i++) {
+ msb |= ( ((long)uuid[i] & 0xff) << (8 * (7 - i)) );
+ lsb |= ( ((long)uuid[i+8] & 0xff) << (8 * (7 - i)) );
+ }
+
+ return new UUID(msb, lsb);
+ }
+
+ private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) {
+ Map<UUID, byte[]> result = new HashMap<UUID, byte[]>();
+
+ final int UUID_SIZE = 16;
+ final int DATALEN_SIZE = 4;
+
+ int len = psshsize;
+ int numentries = 0;
+ int i = 0;
+
+ while (len > 0) {
+ if (len < UUID_SIZE) {
+ Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
+ "UUID: (%d < 16) pssh: %d", len, psshsize));
+ return null;
+ }
+
+ byte[] subset = Arrays.copyOfRange(pssh, i, i + UUID_SIZE);
+ UUID uuid = bytesToUUID(subset);
+ i += UUID_SIZE;
+ len -= UUID_SIZE;
+
+ // get data length
+ if (len < 4) {
+ Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
+ "datalen: (%d < 4) pssh: %d", len, psshsize));
+ return null;
+ }
+
+ subset = Arrays.copyOfRange(pssh, i, i+DATALEN_SIZE);
+ int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) ?
+ ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) |
+ ((subset[1] & 0xff) << 8) | (subset[0] & 0xff) :
+ ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) |
+ ((subset[2] & 0xff) << 8) | (subset[3] & 0xff) ;
+ i += DATALEN_SIZE;
+ len -= DATALEN_SIZE;
+
+ if (len < datalen) {
+ Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
+ "data: (%d < %d) pssh: %d", len, datalen, psshsize));
+ return null;
+ }
+
+ byte[] data = Arrays.copyOfRange(pssh, i, i+datalen);
+
+ // skip the data
+ i += datalen;
+ len -= datalen;
+
+ Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d",
+ numentries, uuid, arrToHex(data), psshsize));
+ numentries++;
+ result.put(uuid, data);
+ }
+
+ return result;
+ }
+
+ }; // DrmInfo
+
+ /**
+ * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm().
+ * Extends MediaDrm.MediaDrmException
+ */
+ public static final class NoDrmSchemeException extends MediaDrmException {
+ public NoDrmSchemeException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Thrown when the device requires DRM provisioning but the provisioning attempt has
+ * failed (for example: network timeout, provisioning server error).
+ * Extends MediaDrm.MediaDrmException
+ */
+ public static final class ProvisioningErrorException extends MediaDrmException {
+ public ProvisioningErrorException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ // Modular DRM helpers
+
+ private class ProvisioningThread extends Thread
+ {
+ public static final int TIMEOUT_MS = 60000;
+
+ private UUID uuid;
+ private String urlStr;
+ private byte[] response;
+ private Object drmLock;
+ private OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate;
+ private MediaPlayer mediaPlayer;
+ private boolean succeeded;
+ private boolean finished;
+ public boolean succeeded() {
+ return succeeded;
+ }
+
+ public ProvisioningThread initialize(MediaDrm.ProvisionRequest request,
+ UUID uuid, MediaPlayer mediaPlayer) {
+ // lock is held by the caller
+ drmLock = mediaPlayer.mDrmLock;
+ onDrmPreparedHandlerDelegate = mediaPlayer.mOnDrmPreparedHandlerDelegate;
+ this.mediaPlayer = mediaPlayer;
+
+ urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
+ this.uuid = uuid;
+
+ Log.v(TAG, String.format("HandleProvisioninig: Thread is initialised url: %s", urlStr));
+ return this;
+ }
+
+ public void run() {
+
+ boolean provisioningSucceeded = false;
+ try {
+ URL url = new URL(urlStr);
+ final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ try {
+ connection.setRequestMethod("POST");
+ connection.setDoOutput(false);
+ connection.setDoInput(true);
+ connection.setConnectTimeout(TIMEOUT_MS);
+ connection.setReadTimeout(TIMEOUT_MS);
+
+ connection.connect();
+ response = Streams.readFully(connection.getInputStream());
+
+ Log.v(TAG, String.format("HandleProvisioninig: Thread run response %d %s",
+ response.length, response));
+ } catch (Exception e) {
+ Log.w(TAG, String.format("HandleProvisioninig: Thread run connect %s url: %s",
+ e, url));
+ } finally {
+ connection.disconnect();
+ }
+ } catch (Exception e) {
+ Log.w(TAG, String.format("HandleProvisioninig: Thread run openConnection %s", e));
+ }
+
+ if (response != null) {
+ try {
+ MediaDrm drm = new MediaDrm(uuid);
+ drm.provideProvisionResponse(response);
+ drm.release();
+ Log.v(TAG, String.format("HandleProvisioninig: Thread run " +
+ "newDrm+provideProvisionResponse SUCCEEDED!"));
+
+ provisioningSucceeded = true;
+ } catch (Exception e) {
+ Log.w(TAG, String.format("HandleProvisioninig: Thread run " +
+ "newDrm+provideProvisionResponse %s", e));
+ }
+ }
+
+ // non-blocking mode needs the lock
+ if (onDrmPreparedHandlerDelegate != null) {
+
+ synchronized (drmLock) {
+ // continuing with prepareDrm
+ if (provisioningSucceeded) {
+ succeeded = mediaPlayer.resumePrepareDrm(uuid);
+ }
+ mediaPlayer.mDrmProvisioningInProgress = false;
+ mediaPlayer.mPrepareDrmInProgress = false;
+ }
+
+ // calling the callback outside the lock
+ onDrmPreparedHandlerDelegate.notifyClient(succeeded);
+ } else { // blocking mode already has the lock
+
+ // continuing with prepareDrm
+ if (provisioningSucceeded) {
+ succeeded = mediaPlayer.resumePrepareDrm(uuid);
+ }
+ mediaPlayer.mDrmProvisioningInProgress = false;
+ mediaPlayer.mPrepareDrmInProgress = false;
+ }
+
+ finished = true;
+ } // run()
+
+ } // ProvisioningThread
+
+ private boolean HandleProvisioninig(UUID uuid)
+ {
+ // the lock is already held by the caller
+
+ if (mDrmProvisioningInProgress) {
+ Log.e(TAG, String.format("HandleProvisioninig: Unexpected mDrmProvisioningInProgress"));
+ return false;
+ }
+
+ MediaDrm.ProvisionRequest provReq = null;
+ try {
+ MediaDrm drm = new MediaDrm(uuid);
+ provReq = drm.getProvisionRequest();
+ drm.release();
+ } catch (Exception e) {
+ Log.e(TAG, String.format("HandleProvisioninig: getProvisionRequest failed with %s", e));
+ return false;
+ }
+
+ Log.v(TAG, String.format("HandleProvisioninig provReq: data %s url %s",
+ (provReq != null) ? provReq.getData() : "-",
+ (provReq != null) ? provReq.getDefaultUrl() : "://")
+ );
+
+ // networking in a background thread
+ mDrmProvisioningInProgress = true;
+
+ mDrmProvisioningThread = new ProvisioningThread().initialize(provReq, uuid, this);
+ mDrmProvisioningThread.start();
+
+ boolean result = false;
+
+ // non-blocking
+ if (mOnDrmPreparedHandlerDelegate != null) {
+ result = true;
+ } else {
+ // if blocking mode, wait till provisioning is done
+ try {
+ mDrmProvisioningThread.join();
+ } catch (Exception e) {
+ Log.w(TAG, String.format("HandleProvisioninig: Thread.join Exception %s", e));
+ }
+ result = mDrmProvisioningThread.succeeded();
+ // no longer need the thread
+ mDrmProvisioningThread = null;
+ }
+
+ return result;
+ }
+
+ private boolean resumePrepareDrm(UUID uuid)
+ {
+ // mDrmLock is guaranteed to be held
+ boolean success = false;
+ try {
+ boolean allowOpenSession = true; // resuming
+ _prepareDrm(getByteArrayFromUUID(uuid), allowOpenSession ? 1 : 0);
+
+ mDrmUUID = uuid;
+ mActiveDrmScheme = true;
+
+ success = true;
+ } catch (Exception e) {
+ Log.w(TAG, String.format("HandleProvisioninig: " +
+ "Thread run _prepareDrm resume failed with %s", e));
+ }
+
+ return success;
+ }
+
+ private void resetDrmState()
+ {
+ synchronized (mDrmLock) {
+ mDrmInfoResolved = false;
+ mDrmInfo = null;
+
+ if (mDrmProvisioningThread != null) {
+ // timeout; relying on HttpUrlConnection
+ try {
+ mDrmProvisioningThread.join();
+ }
+ catch (InterruptedException e) {
+ Log.w(TAG, String.format("resetDrmState: ProvThread.join Exception %s", e));
+ }
+ mDrmProvisioningThread = null;
+ }
+
+ mPrepareDrmInProgress = false;
+ } // synchronized
+ }
+
+ private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
+ long msb = uuid.getMostSignificantBits();
+ long lsb = uuid.getLeastSignificantBits();
+
+ byte[] uuidBytes = new byte[16];
+ for (int i = 0; i < 8; ++i) {
+ uuidBytes[i] = (byte)(msb >>> (8 * (7 - i)));
+ uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i)));
+ }
+
+ return uuidBytes;
+ }
+
+ // Modular DRM end
+
/*
* Test whether a given video scaling mode is supported.
*/
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index af59d818f447..5e8135f5d4b2 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -55,6 +55,90 @@
#include <binder/IServiceManager.h>
#include "android_util_Binder.h"
+
+// Modular DRM begin
+#include <media/drm/DrmAPI.h>
+
+#define FIND_CLASS(var, className) \
+var = env->FindClass(className); \
+LOG_FATAL_IF(! (var), "Unable to find class " className);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
+
+#define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \
+var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \
+LOG_FATAL_IF(! (var), "Unable to find method " fieldName);
+
+#define GET_STATIC_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+var = env->GetStaticFieldID(clazz, fieldName, fieldDescriptor); \
+LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
+
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+struct RequestFields {
+ jfieldID data;
+ jfieldID defaultUrl;
+ jfieldID requestType;
+};
+
+struct HashmapFields {
+ jmethodID init;
+ jmethodID get;
+ jmethodID put;
+ jmethodID entrySet;
+};
+
+struct SetFields {
+ jmethodID iterator;
+};
+
+struct IteratorFields {
+ jmethodID next;
+ jmethodID hasNext;
+};
+
+struct EntryFields {
+ jmethodID getKey;
+ jmethodID getValue;
+};
+
+struct KeyTypes {
+ jint kKeyTypeStreaming;
+ jint kKeyTypeOffline;
+ jint kKeyTypeRelease;
+};
+
+static KeyTypes gKeyTypes;
+
+struct KeyRequestTypes {
+ jint kKeyRequestTypeInitial;
+ jint kKeyRequestTypeRenewal;
+ jint kKeyRequestTypeRelease;
+};
+
+static KeyRequestTypes gKeyRequestTypes;
+
+struct StateExceptionFields {
+ jmethodID init;
+ jclass classId;
+};
+
+struct drm_fields_t {
+ RequestFields keyRequest;
+ HashmapFields hashmap;
+ SetFields set;
+ IteratorFields iterator;
+ EntryFields entry;
+ StateExceptionFields stateException;
+ jclass stringClassId;
+};
+
+static drm_fields_t gFields;
+
+// Modular DRM end
+
// ----------------------------------------------------------------------------
using namespace android;
@@ -953,6 +1037,55 @@ android_media_MediaPlayer_native_init(JNIEnv *env)
env->DeleteLocalRef(clazz);
gBufferingParamsFields.init(env);
+
+ // Modular DRM
+ FIND_CLASS(clazz, "android/media/MediaDrm");
+ if (clazz) {
+ jfieldID field;
+ GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I");
+ gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_OFFLINE", "I");
+ gKeyTypes.kKeyTypeOffline = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_RELEASE", "I");
+ gKeyTypes.kKeyTypeRelease = env->GetStaticIntField(clazz, field);
+
+ env->DeleteLocalRef(clazz);
+ } else {
+ ALOGE("JNI getKeyRequest android_media_MediaPlayer_native_init couldn't "
+ "get clazz android/media/MediaDrm");
+ }
+
+ FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest");
+ if (clazz) {
+ GET_FIELD_ID(gFields.keyRequest.data, clazz, "mData", "[B");
+ GET_FIELD_ID(gFields.keyRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;");
+ GET_FIELD_ID(gFields.keyRequest.requestType, clazz, "mRequestType", "I");
+
+ jfieldID field;
+ GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_INITIAL", "I");
+ gKeyRequestTypes.kKeyRequestTypeInitial = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RENEWAL", "I");
+ gKeyRequestTypes.kKeyRequestTypeRenewal = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RELEASE", "I");
+ gKeyRequestTypes.kKeyRequestTypeRelease = env->GetStaticIntField(clazz, field);
+
+ env->DeleteLocalRef(clazz);
+ } else {
+ ALOGE("JNI getKeyRequest android_media_MediaPlayer_native_init couldn't "
+ "get clazz android/media/MediaDrm$KeyRequest");
+ }
+
+ FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
+ if (clazz) {
+ GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(ILjava/lang/String;)V");
+ gFields.stateException.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
+
+ env->DeleteLocalRef(clazz);
+ } else {
+ ALOGE("JNI getKeyRequest android_media_MediaPlayer_native_init couldn't "
+ "get clazz android/media/MediaDrm$MediaDrmStateException");
+ }
+
gPlaybackParamsFields.init(env);
gSyncParamsFields.init(env);
}
@@ -1126,6 +1259,441 @@ android_media_MediaPlayer_setNextMediaPlayer(JNIEnv *env, jobject thiz, jobject
;
}
+/////////////////////////////////////////////////////////////////////////////////////
+// Modular DRM begin
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static void throwDrmStateException(JNIEnv *env, const char *msg, status_t err)
+{
+ ALOGE("Illegal DRM state exception: %s (%d)", msg, err);
+
+ jobject exception = env->NewObject(gFields.stateException.classId,
+ gFields.stateException.init, static_cast<int>(err),
+ env->NewStringUTF(msg));
+ env->Throw(static_cast<jthrowable>(exception));
+}
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static bool throwDrmExceptionAsNecessary(JNIEnv *env, status_t err, const char *msg = NULL)
+{
+ const char *drmMessage = "Unknown DRM Msg";
+
+ switch (err) {
+ case ERROR_DRM_UNKNOWN:
+ drmMessage = "General DRM error";
+ break;
+ case ERROR_DRM_NO_LICENSE:
+ drmMessage = "No license";
+ break;
+ case ERROR_DRM_LICENSE_EXPIRED:
+ drmMessage = "License expired";
+ break;
+ case ERROR_DRM_SESSION_NOT_OPENED:
+ drmMessage = "Session not opened";
+ break;
+ case ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED:
+ drmMessage = "Not initialized";
+ break;
+ case ERROR_DRM_DECRYPT:
+ drmMessage = "Decrypt error";
+ break;
+ case ERROR_DRM_CANNOT_HANDLE:
+ drmMessage = "Unsupported scheme or data format";
+ break;
+ case ERROR_DRM_TAMPER_DETECTED:
+ drmMessage = "Invalid state";
+ break;
+ default:
+ break;
+ }
+
+ String8 vendorMessage;
+ if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) {
+ vendorMessage = String8::format("DRM vendor-defined error: %d", err);
+ drmMessage = vendorMessage.string();
+ }
+
+ if (err == BAD_VALUE) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", msg);
+ return true;
+ } else if (err == ERROR_DRM_NOT_PROVISIONED) {
+ jniThrowException(env, "android/media/NotProvisionedException", msg);
+ return true;
+ } else if (err == ERROR_DRM_RESOURCE_BUSY) {
+ jniThrowException(env, "android/media/ResourceBusyException", msg);
+ return true;
+ } else if (err == ERROR_DRM_DEVICE_REVOKED) {
+ jniThrowException(env, "android/media/DeniedByServerException", msg);
+ return true;
+ } else if (err == DEAD_OBJECT) {
+ jniThrowException(env, "android/media/MediaDrmResetException",
+ "mediaserver died");
+ return true;
+ } else if (err != OK) {
+ String8 errbuf;
+ if (drmMessage != NULL) {
+ if (msg == NULL) {
+ msg = drmMessage;
+ } else {
+ errbuf = String8::format("%s: %s", msg, drmMessage);
+ msg = errbuf.string();
+ }
+ }
+ throwDrmStateException(env, msg, err);
+ return true;
+ }
+ return false;
+}
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static jbyteArray VectorToJByteArray(JNIEnv *env, Vector<uint8_t> const &vector)
+{
+ size_t length = vector.size();
+ jbyteArray result = env->NewByteArray(length);
+ if (result != NULL) {
+ env->SetByteArrayRegion(result, 0, length, (jbyte *)vector.array());
+ }
+ return result;
+}
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray)
+{
+ Vector<uint8_t> vector;
+ size_t length = env->GetArrayLength(byteArray);
+ vector.insertAt((size_t)0, length);
+ env->GetByteArrayRegion(byteArray, 0, length, (jbyte *)vector.editArray());
+ return vector;
+}
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static String8 JStringToString8(JNIEnv *env, jstring const &jstr)
+{
+ String8 result;
+
+ const char *s = env->GetStringUTFChars(jstr, NULL);
+ if (s) {
+ result = s;
+ env->ReleaseStringUTFChars(jstr, s);
+ }
+ return result;
+}
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env,
+ jobject &hashMap, bool* pIsOK)
+{
+ jclass clazz = gFields.stringClassId;
+ KeyedVector<String8, String8> keyedVector;
+ *pIsOK = true;
+
+ jobject entrySet = env->CallObjectMethod(hashMap, gFields.hashmap.entrySet);
+ if (entrySet) {
+ jobject iterator = env->CallObjectMethod(entrySet, gFields.set.iterator);
+ if (iterator) {
+ jboolean hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext);
+ while (hasNext) {
+ jobject entry = env->CallObjectMethod(iterator, gFields.iterator.next);
+ if (entry) {
+ jobject obj = env->CallObjectMethod(entry, gFields.entry.getKey);
+ if (obj == NULL || !env->IsInstanceOf(obj, clazz)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "HashMap key is not a String");
+ env->DeleteLocalRef(entry);
+ *pIsOK = false;
+ break;
+ }
+ jstring jkey = static_cast<jstring>(obj);
+
+ obj = env->CallObjectMethod(entry, gFields.entry.getValue);
+ if (obj == NULL || !env->IsInstanceOf(obj, clazz)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "HashMap value is not a String");
+ env->DeleteLocalRef(entry);
+ *pIsOK = false;
+ break;
+ }
+ jstring jvalue = static_cast<jstring>(obj);
+
+ String8 key = JStringToString8(env, jkey);
+ String8 value = JStringToString8(env, jvalue);
+ keyedVector.add(key, value);
+
+ env->DeleteLocalRef(jkey);
+ env->DeleteLocalRef(jvalue);
+ hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext);
+ }
+ env->DeleteLocalRef(entry);
+ }
+ env->DeleteLocalRef(iterator);
+ }
+ env->DeleteLocalRef(entrySet);
+ }
+ return keyedVector;
+}
+
+static void android_media_MediaPlayer_prepareDrm(JNIEnv *env, jobject thiz,
+ jbyteArray uuidObj, jint mode)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (uuidObj == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ Vector<uint8_t> uuid = JByteArrayToVector(env, uuidObj);
+
+ if (uuid.size() != 16) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalArgumentException",
+ "invalid UUID size, expected 16 bytes");
+ return;
+ }
+
+ status_t err = mp->prepareDrm(uuid.array(), mode);
+ if (err != OK) {
+ if (err == INVALID_OPERATION) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalStateException",
+ "The player is not prepared yet.");
+ } else if (err == ERROR_DRM_CANNOT_HANDLE) {
+ jniThrowException(
+ env,
+ "android/media/UnsupportedSchemeException",
+ "Failed to instantiate drm object.");
+ } else {
+ throwDrmExceptionAsNecessary(env, err, "Failed to prepare DRM scheme");
+ }
+ }
+}
+
+static void android_media_MediaPlayer_releaseDrm(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ status_t err = mp->releaseDrm();
+ if (err != OK) {
+ if (err == INVALID_OPERATION) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalStateException",
+ "The player is not prepared yet.");
+ }
+ }
+}
+
+static jobject android_media_MediaPlayer_getKeyRequest(JNIEnv *env, jobject thiz, jbyteArray jscope,
+ jstring jmimeType, jint jkeyType, jobject joptParams)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ Vector<uint8_t> scope;
+ if (jscope != NULL) {
+ scope = JByteArrayToVector(env, jscope);
+ }
+
+ String8 mimeType;
+ if (jmimeType != NULL) {
+ mimeType = JStringToString8(env, jmimeType);
+ }
+
+ DrmPlugin::KeyType keyType;
+ if (jkeyType == gKeyTypes.kKeyTypeStreaming) {
+ keyType = DrmPlugin::kKeyType_Streaming;
+ } else if (jkeyType == gKeyTypes.kKeyTypeOffline) {
+ keyType = DrmPlugin::kKeyType_Offline;
+ } else if (jkeyType == gKeyTypes.kKeyTypeRelease) {
+ keyType = DrmPlugin::kKeyType_Release;
+ } else {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "invalid keyType");
+ return NULL;
+ }
+
+ KeyedVector<String8, String8> optParams;
+ if (joptParams != NULL) {
+ bool isOK;
+ optParams = HashMapToKeyedVector(env, joptParams, &isOK);
+ if (!isOK) {
+ return NULL;
+ }
+ }
+
+ Vector<uint8_t> request;
+ String8 defaultUrl;
+ DrmPlugin::KeyRequestType keyRequestType;
+ status_t err = mp->getKeyRequest(scope, mimeType, keyType, optParams, request, defaultUrl,
+ keyRequestType);
+
+ if (throwDrmExceptionAsNecessary(env, err, "Failed to get key request")) {
+ return NULL;
+ }
+
+ ALOGV("JNI getKeyRequest err %d request %d url %s keyReqType %d",
+ err, (int)request.size(), defaultUrl.string(), (int)keyRequestType);
+
+ // Fill out return obj
+ jclass clazz;
+ FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest");
+
+ jobject keyObj = NULL;
+
+ if (clazz) {
+ keyObj = env->AllocObject(clazz);
+ jbyteArray jrequest = VectorToJByteArray(env, request);
+ env->SetObjectField(keyObj, gFields.keyRequest.data, jrequest);
+
+ jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string());
+ env->SetObjectField(keyObj, gFields.keyRequest.defaultUrl, jdefaultUrl);
+
+ switch (keyRequestType) {
+ case DrmPlugin::kKeyRequestType_Initial:
+ env->SetIntField(keyObj, gFields.keyRequest.requestType,
+ gKeyRequestTypes.kKeyRequestTypeInitial);
+ break;
+ case DrmPlugin::kKeyRequestType_Renewal:
+ env->SetIntField(keyObj, gFields.keyRequest.requestType,
+ gKeyRequestTypes.kKeyRequestTypeRenewal);
+ break;
+ case DrmPlugin::kKeyRequestType_Release:
+ env->SetIntField(keyObj, gFields.keyRequest.requestType,
+ gKeyRequestTypes.kKeyRequestTypeRelease);
+ break;
+ default:
+ throwDrmStateException(env, "MediaPlayer/DRM plugin failure: unknown "
+ "key request type", ERROR_DRM_UNKNOWN);
+ break;
+ }
+ }
+
+ return keyObj;
+}
+
+static jbyteArray android_media_MediaPlayer_provideKeyResponse(JNIEnv *env, jobject thiz,
+ jbyteArray jreleaseKeySetId, jbyteArray jresponse)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ if (jresponse == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "key response is null");
+ return NULL;
+ }
+
+ Vector<uint8_t> releaseKeySetId;
+ if (jreleaseKeySetId != NULL) {
+ releaseKeySetId = JByteArrayToVector(env, jreleaseKeySetId);
+ }
+
+ Vector<uint8_t> response(JByteArrayToVector(env, jresponse));
+ Vector<uint8_t> keySetId;
+
+ status_t err = mp->provideKeyResponse(releaseKeySetId, response, keySetId);
+
+ if (throwDrmExceptionAsNecessary(env, err, "Failed to handle key response")) {
+ return NULL;
+ }
+ return VectorToJByteArray(env, keySetId);
+}
+
+static void android_media_MediaPlayer_restoreKeys(JNIEnv *env, jobject thiz, jbyteArray jkeySetId)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (jkeySetId == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "invalid keyType");
+ return;
+ }
+
+ Vector<uint8_t> keySetId;
+ keySetId = JByteArrayToVector(env, jkeySetId);
+
+ status_t err = mp->restoreKeys(keySetId);
+
+ ALOGV("JNI restoreKeys err %d ", err);
+ throwDrmExceptionAsNecessary(env, err, "Failed to restore keys");
+}
+
+static jstring android_media_MediaPlayer_getDrmPropertyString(JNIEnv *env, jobject thiz,
+ jstring jname)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ if (jname == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "property name String is null");
+ return NULL;
+ }
+
+ String8 name = JStringToString8(env, jname);
+ String8 value;
+
+ status_t err = mp->getDrmPropertyString(name, value);
+
+ ALOGV("JNI getPropertyString err %d", err);
+
+ if (throwDrmExceptionAsNecessary(env, err, "Failed to get property")) {
+ return NULL;
+ }
+
+ return env->NewStringUTF(value.string());
+}
+
+static void android_media_MediaPlayer_setDrmPropertyString(JNIEnv *env, jobject thiz,
+ jstring jname, jstring jvalue)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (jname == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "property name String is null");
+ return;
+ }
+
+ if (jvalue == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "property value String is null");
+ return;
+ }
+
+ String8 name = JStringToString8(env, jname);
+ String8 value = JStringToString8(env, jvalue);
+
+ status_t err = mp->setDrmPropertyString(name, value);
+
+ ALOGV("JNI setPropertyString err %d", err);
+ throwDrmExceptionAsNecessary(env, err, "Failed to set property");
+}
+// Modular DRM end
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
@@ -1179,6 +1747,15 @@ static const JNINativeMethod gMethods[] = {
{"native_pullBatteryData", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_pullBatteryData},
{"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer_setRetransmitEndpoint},
{"setNextMediaPlayer", "(Landroid/media/MediaPlayer;)V", (void *)android_media_MediaPlayer_setNextMediaPlayer},
+ // Modular DRM
+ { "_prepareDrm", "([BI)V", (void *)android_media_MediaPlayer_prepareDrm },
+ { "_releaseDrm", "()V", (void *)android_media_MediaPlayer_releaseDrm },
+ { "_getKeyRequest", "([BLjava/lang/String;ILjava/util/Map;)" "Landroid/media/MediaDrm$KeyRequest;",
+ (void *)android_media_MediaPlayer_getKeyRequest },
+ { "_provideKeyResponse", "([B[B)[B", (void *)android_media_MediaPlayer_provideKeyResponse },
+ { "_getDrmPropertyString", "(Ljava/lang/String;)Ljava/lang/String;", (void *)android_media_MediaPlayer_getDrmPropertyString },
+ { "_setDrmPropertyString", "(Ljava/lang/String;Ljava/lang/String;)V",(void *)android_media_MediaPlayer_setDrmPropertyString },
+ { "_restoreKeys", "([B)V", (void *)android_media_MediaPlayer_restoreKeys },
};
// This function only registers the native methods