Merge "MediaPlayer/ModularDrm API review" into oc-dev
diff --git a/api/current.txt b/api/current.txt
index 5b55fdc..87ea881 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22774,7 +22774,7 @@
     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.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
     method public android.os.PersistableBundle getMetrics();
     method public android.media.PlaybackParams getPlaybackParams();
     method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -22788,7 +22788,7 @@
     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) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+    method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningNetworkErrorException, android.media.MediaPlayer.ProvisioningServerErrorException, 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;
@@ -22815,7 +22815,7 @@
     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 setOnDrmConfigListener(android.media.MediaPlayer.OnDrmConfigListener);
+    method public void setOnDrmConfigHelper(android.media.MediaPlayer.OnDrmConfigHelper);
     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);
@@ -22856,6 +22856,10 @@
     field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
     field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
     field public static final java.lang.String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
+    field public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; // 0x3
+    field public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; // 0x1
+    field public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; // 0x2
+    field public static final int PREPARE_DRM_STATUS_SUCCESS = 0; // 0x0
     field public static final int SEEK_CLOSEST = 3; // 0x3
     field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
     field public static final int SEEK_NEXT_SYNC = 1; // 0x1
@@ -22865,7 +22869,6 @@
   }
 
   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();
   }
@@ -22897,7 +22900,7 @@
     method public abstract void onCompletion(android.media.MediaPlayer);
   }
 
-  public static abstract interface MediaPlayer.OnDrmConfigListener {
+  public static abstract interface MediaPlayer.OnDrmConfigHelper {
     method public abstract void onDrmConfig(android.media.MediaPlayer);
   }
 
@@ -22906,7 +22909,7 @@
   }
 
   public static abstract interface MediaPlayer.OnDrmPreparedListener {
-    method public abstract void onDrmPrepared(android.media.MediaPlayer, boolean);
+    method public abstract void onDrmPrepared(android.media.MediaPlayer, int);
   }
 
   public static abstract interface MediaPlayer.OnErrorListener {
@@ -22937,8 +22940,12 @@
     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 final class MediaPlayer.ProvisioningNetworkErrorException extends android.media.MediaDrmException {
+    ctor public MediaPlayer.ProvisioningNetworkErrorException(java.lang.String);
+  }
+
+  public static final class MediaPlayer.ProvisioningServerErrorException extends android.media.MediaDrmException {
+    ctor public MediaPlayer.ProvisioningServerErrorException(java.lang.String);
   }
 
   public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 23a8cad..b85c83c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -24610,7 +24610,7 @@
     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.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
     method public android.os.PersistableBundle getMetrics();
     method public android.media.PlaybackParams getPlaybackParams();
     method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -24624,7 +24624,7 @@
     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) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+    method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningNetworkErrorException, android.media.MediaPlayer.ProvisioningServerErrorException, 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;
@@ -24651,7 +24651,7 @@
     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 setOnDrmConfigListener(android.media.MediaPlayer.OnDrmConfigListener);
+    method public void setOnDrmConfigHelper(android.media.MediaPlayer.OnDrmConfigHelper);
     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);
@@ -24692,6 +24692,10 @@
     field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
     field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
     field public static final java.lang.String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
+    field public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; // 0x3
+    field public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; // 0x1
+    field public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; // 0x2
+    field public static final int PREPARE_DRM_STATUS_SUCCESS = 0; // 0x0
     field public static final int SEEK_CLOSEST = 3; // 0x3
     field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
     field public static final int SEEK_NEXT_SYNC = 1; // 0x1
@@ -24701,7 +24705,6 @@
   }
 
   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();
   }
@@ -24733,7 +24736,7 @@
     method public abstract void onCompletion(android.media.MediaPlayer);
   }
 
-  public static abstract interface MediaPlayer.OnDrmConfigListener {
+  public static abstract interface MediaPlayer.OnDrmConfigHelper {
     method public abstract void onDrmConfig(android.media.MediaPlayer);
   }
 
@@ -24742,7 +24745,7 @@
   }
 
   public static abstract interface MediaPlayer.OnDrmPreparedListener {
-    method public abstract void onDrmPrepared(android.media.MediaPlayer, boolean);
+    method public abstract void onDrmPrepared(android.media.MediaPlayer, int);
   }
 
   public static abstract interface MediaPlayer.OnErrorListener {
@@ -24773,8 +24776,12 @@
     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 final class MediaPlayer.ProvisioningNetworkErrorException extends android.media.MediaDrmException {
+    ctor public MediaPlayer.ProvisioningNetworkErrorException(java.lang.String);
+  }
+
+  public static final class MediaPlayer.ProvisioningServerErrorException extends android.media.MediaDrmException {
+    ctor public MediaPlayer.ProvisioningServerErrorException(java.lang.String);
   }
 
   public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 3373ab7..3a57f8a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -22881,7 +22881,7 @@
     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.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
     method public android.os.PersistableBundle getMetrics();
     method public android.media.PlaybackParams getPlaybackParams();
     method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -22895,7 +22895,7 @@
     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) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+    method public void prepareDrm(java.util.UUID) throws android.media.MediaPlayer.ProvisioningNetworkErrorException, android.media.MediaPlayer.ProvisioningServerErrorException, 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;
@@ -22922,7 +22922,7 @@
     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 setOnDrmConfigListener(android.media.MediaPlayer.OnDrmConfigListener);
+    method public void setOnDrmConfigHelper(android.media.MediaPlayer.OnDrmConfigHelper);
     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);
@@ -22963,6 +22963,10 @@
     field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
     field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
     field public static final java.lang.String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
+    field public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; // 0x3
+    field public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; // 0x1
+    field public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; // 0x2
+    field public static final int PREPARE_DRM_STATUS_SUCCESS = 0; // 0x0
     field public static final int SEEK_CLOSEST = 3; // 0x3
     field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
     field public static final int SEEK_NEXT_SYNC = 1; // 0x1
@@ -22972,7 +22976,6 @@
   }
 
   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();
   }
@@ -23004,7 +23007,7 @@
     method public abstract void onCompletion(android.media.MediaPlayer);
   }
 
-  public static abstract interface MediaPlayer.OnDrmConfigListener {
+  public static abstract interface MediaPlayer.OnDrmConfigHelper {
     method public abstract void onDrmConfig(android.media.MediaPlayer);
   }
 
@@ -23013,7 +23016,7 @@
   }
 
   public static abstract interface MediaPlayer.OnDrmPreparedListener {
-    method public abstract void onDrmPrepared(android.media.MediaPlayer, boolean);
+    method public abstract void onDrmPrepared(android.media.MediaPlayer, int);
   }
 
   public static abstract interface MediaPlayer.OnErrorListener {
@@ -23044,8 +23047,12 @@
     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 final class MediaPlayer.ProvisioningNetworkErrorException extends android.media.MediaDrmException {
+    ctor public MediaPlayer.ProvisioningNetworkErrorException(java.lang.String);
+  }
+
+  public static final class MediaPlayer.ProvisioningServerErrorException extends android.media.MediaDrmException {
+    ctor public MediaPlayer.ProvisioningServerErrorException(java.lang.String);
   }
 
   public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 9386246..d5efc97 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1007,13 +1007,14 @@
      * @param context the Context to use when resolving the Uri
      * @param uri the Content URI of the data you want to play
      * @param headers the headers to be sent together with the request for the data
-     *                Note that the cross domain redirection is allowed by default, but that can be
-     *                changed with key/value pairs through the headers parameter with
-     *                "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
-     *                to disallow or allow cross domain redirection.
      *                The headers must not include cookies. Instead, use the cookies param.
      * @param cookies the cookies to be sent together with the request
      * @throws IllegalStateException if it is called in an invalid state
+     *
+     * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
+     * but that can be changed with key/value pairs through the headers parameter with
+     * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
+     * disallow or allow cross domain redirection.
      */
     public void setDataSource(@NonNull Context context, @NonNull Uri uri,
             @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies)
@@ -1056,11 +1057,12 @@
      * @param context the Context to use when resolving the Uri
      * @param uri the Content URI of the data you want to play
      * @param headers the headers to be sent together with the request for the data
-     *                Note that the cross domain redirection is allowed by default, but that can be
-     *                changed with key/value pairs through the headers parameter with
-     *                "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
-     *                to disallow or allow cross domain redirection.
      * @throws IllegalStateException if it is called in an invalid state
+     *
+     * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
+     * but that can be changed with key/value pairs through the headers parameter with
+     * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
+     * disallow or allow cross domain redirection.
      */
     public void setDataSource(@NonNull Context context, @NonNull Uri uri,
             @Nullable Map<String, String> headers)
@@ -1981,7 +1983,7 @@
         mOnSubtitleDataListener = null;
 
         // Modular DRM clean up
-        mOnDrmConfigListener = null;
+        mOnDrmConfigHelper = null;
         mOnDrmInfoHandlerDelegate = null;
         mOnDrmPreparedHandlerDelegate = null;
         resetDrmState();
@@ -3905,11 +3907,11 @@
      * '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.
+     * The only allowed DRM calls in this listener are {@code getDrmPropertyString}
+     * and {@code setDrmPropertyString}.
      *
      */
-    public interface OnDrmConfigListener
+    public interface OnDrmConfigHelper
     {
         /**
          * Called to give the app the opportunity to configure DRM before the session is created
@@ -3922,19 +3924,19 @@
     /**
      * Register a callback to be invoked for configuration of the DRM object before
      * the session is created.
-     * The callback will be invoked synchronously half-way into the execution
+     * The callback will be invoked synchronously during the execution
      * of {@link #prepareDrm(UUID uuid)}.
      *
      * @param listener the callback that will be run
      */
-    public void setOnDrmConfigListener(OnDrmConfigListener listener)
+    public void setOnDrmConfigHelper(OnDrmConfigHelper listener)
     {
         synchronized (mDrmLock) {
-            mOnDrmConfigListener = listener;
+            mOnDrmConfigHelper = listener;
         } // synchronized
     }
 
-    private OnDrmConfigListener mOnDrmConfigListener;
+    private OnDrmConfigHelper mOnDrmConfigHelper;
 
     /**
      * Interface definition of a callback to be invoked when the
@@ -3946,7 +3948,7 @@
          * 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
+         * @param drmInfo DRM info of the source including PSSH, and subset
          *                of crypto schemes supported by this device
          */
         public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo);
@@ -3982,6 +3984,41 @@
 
     private OnDrmInfoHandlerDelegate mOnDrmInfoHandlerDelegate;
 
+
+    /**
+     * The status codes for {@link OnDrmPreparedListener#onDrmPrepared} listener.
+     * <p>
+     *
+     * DRM preparation has succeeded.
+     */
+    public static final int PREPARE_DRM_STATUS_SUCCESS = 0;
+
+    /**
+     * The device required DRM provisioning but couldn't reach the provisioning server.
+     */
+    public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1;
+
+    /**
+     * The device required DRM provisioning but the provisioning server denied the request.
+     */
+    public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2;
+
+    /**
+     * The DRM preparation has failed .
+     */
+    public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3;
+
+
+    /** @hide */
+    @IntDef({
+        PREPARE_DRM_STATUS_SUCCESS,
+        PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR,
+        PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR,
+        PREPARE_DRM_STATUS_PREPARATION_ERROR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PrepareDrmStatusCode {}
+
     /**
      * Interface definition of a callback to notify the app when the
      * DRM is ready for key request/response
@@ -3992,9 +4029,13 @@
          * 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
+         * @param status the result of DRM preparation which can be
+         * {@link #PREPARE_DRM_STATUS_SUCCESS},
+         * {@link #PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR},
+         * {@link #PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR}, or
+         * {@link #PREPARE_DRM_STATUS_PREPARATION_ERROR}.
          */
-        public void onDrmPrepared(MediaPlayer mp, boolean success);
+        public void onDrmPrepared(MediaPlayer mp, @PrepareDrmStatusCode int status);
     }
 
     /**
@@ -4038,30 +4079,28 @@
             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);
-                    }
-                };
+                mHandler = handler;
+            } else {
+                // handler == null
+                // Will let OnDrmInfoListener be called in mEventHandler similar to other
+                // legacy notifications. This is because MEDIA_DRM_INFO's notification has to be
+                // sent before MEDIA_PREPARED's (i.e., in the same order they are issued by
+                // mediaserver). As a result, the callback has to be called directly by
+                // EventHandler.handleMessage similar to onPrepared.
             }
         }
 
         void notifyClient(DrmInfo drmInfo) {
-            if ( mHandler != null ) {
-                Message msg = new Message();  // no message type needed
-                msg.obj = drmInfo;
-                mHandler.sendMessage(msg);
+            if (mHandler != null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                       mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo);
+                    }
+                });
             }
-            else {  // no handler: direct call
+            else {  // no handler: direct call by mEventHandler
                 mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo);
             }
         }
@@ -4078,31 +4117,26 @@
             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);
-                    }
-                };
+                mHandler = handler;
+            } else if (mEventHandler != null) {
+                // Otherwise, use mEventHandler
+                mHandler = mEventHandler;
+            } else {
+                Log.e(TAG, "OnDrmPreparedHandlerDelegate: Unexpected null mEventHandler");
             }
         }
 
-        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);
+        void notifyClient(int status) {
+            if (mHandler != null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mOnDrmPreparedListener.onDrmPrepared(mMediaPlayer, status);
+                    }
+                });
+            } else {
+                Log.e(TAG, "OnDrmPreparedHandlerDelegate:notifyClient: Unexpected null mHandler");
             }
         }
     }
@@ -4137,7 +4171,7 @@
     /**
      * Prepares the DRM for the current source
      * <p>
-     * If {@code OnDrmConfigListener} is registered, it will be called half-way into
+     * If {@code OnDrmConfigHelper} is registered, it will be called during
      * 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}
@@ -4148,9 +4182,9 @@
      * complete depending on the network connectivity.
      * If {@code 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
+     * will be called when provisioning and preparation has finished. If a
      * {@code OnDrmPreparedListener} is not registered, prepareDrm() waits till provisioning
-     * and preperation has finished, i.e., runs in blocking mode.
+     * and preparation has finished, i.e., runs in blocking mode.
      * <p>
      * If {@code OnDrmPreparedListener} is registered, it is called to indicate the DRM
      * session being ready. The application should not make any assumption about its call
@@ -4158,18 +4192,23 @@
      * execute the listener (unless the listener is registered with a handler thread).
      * <p>
      *
-     * @param uuid The UUID of the crypto scheme.
+     * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
+     * from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}.
      *
-     * @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
+     * @throws IllegalStateException              if called before prepare(), or the DRM was
+     *                                            prepared already
+     * @throws UnsupportedSchemeException         if the crypto scheme is not supported
+     * @throws ResourceBusyException              if required DRM resources are in use
+     * @throws ProvisioningNetworkErrorException  if provisioning is required but failed due to a
+     *                                            network error
+     * @throws ProvisioningServerErrorException   if provisioning is required but failed due to
+     *                                            the request denied by the provisioning server
      */
     public void prepareDrm(@NonNull UUID uuid)
-            throws UnsupportedSchemeException,
-                   ResourceBusyException, ProvisioningErrorException
+            throws UnsupportedSchemeException, ResourceBusyException,
+                   ProvisioningNetworkErrorException, ProvisioningServerErrorException
     {
-        Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigListener: " + mOnDrmConfigListener);
+        Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper);
 
         boolean allDoneWithoutProvisioning = false;
         // get a snapshot as we'll use them outside the lock
@@ -4177,7 +4216,7 @@
 
         synchronized (mDrmLock) {
 
-            // only allowing if tied to a protected source; might releax for releasing offline keys
+            // only allowing if tied to a protected source; might relax for releasing offline keys
             if (mDrmInfo == null) {
                 final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " +
                         "DRM info be retrieved before this call.";
@@ -4226,8 +4265,8 @@
 
 
         // call the callback outside the lock
-        if (mOnDrmConfigListener != null)  {
-            mOnDrmConfigListener.onDrmConfig(this);
+        if (mOnDrmConfigHelper != null)  {
+            mOnDrmConfigHelper.onDrmConfig(this);
         }
 
         synchronized (mDrmLock) {
@@ -4251,15 +4290,33 @@
                 Log.w(TAG, "prepareDrm: NotProvisionedException");
 
                 // handle provisioning internally; it'll reset mPrepareDrmInProgress
-                boolean result = HandleProvisioninig(uuid);
+                int 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 = "prepareDrm: Provisioning was required but failed.";
-                    Log.e(TAG, msg);
+                if (result != PREPARE_DRM_STATUS_SUCCESS) {
                     earlyExit = true;
-                    throw new ProvisioningErrorException(msg);
+                    String msg;
+
+                    switch (result) {
+                    case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR:
+                        msg = "prepareDrm: Provisioning was required but failed " +
+                                "due to a network error.";
+                        Log.e(TAG, msg);
+                        throw new ProvisioningNetworkErrorException(msg);
+
+                    case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR:
+                        msg = "prepareDrm: Provisioning was required but the request " +
+                                "was denied by the server.";
+                        Log.e(TAG, msg);
+                        throw new ProvisioningServerErrorException(msg);
+
+                    case PREPARE_DRM_STATUS_PREPARATION_ERROR:
+                    default: // default for safeguard
+                        msg = "prepareDrm: Post-provisioning preparation failed.";
+                        Log.e(TAG, msg);
+                        throw new IllegalStateException(msg);
+                    }
                 }
                 // nothing else to do;
                 // if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup
@@ -4281,7 +4338,7 @@
         // if finished successfully without provisioning, call the callback outside the lock
         if (allDoneWithoutProvisioning) {
             if (onDrmPreparedHandlerDelegate != null)
-                onDrmPreparedHandlerDelegate.notifyClient(true /*success*/);
+                onDrmPreparedHandlerDelegate.notifyClient(PREPARE_DRM_STATUS_SUCCESS);
         }
 
     }
@@ -4291,6 +4348,10 @@
 
     /**
      * Releases the DRM session
+     * <p>
+     * The player has to have an active DRM session and be in stopped, or prepared
+     * state before this call is made.
+     * A {@code reset()} call will release the DRM session implicitly.
      *
      * @throws NoDrmSchemeException if there is no active DRM session to release
      */
@@ -4307,7 +4368,7 @@
 
             try {
                 // we don't have the player's state in this layer. The below call raises
-                // exception if we're in a non-stopped/idle state.
+                // exception if we're in a non-stopped/prepared state.
 
                 // for cleaning native/mediaserver crypto object
                 _releaseDrm();
@@ -4316,9 +4377,11 @@
                 cleanDrmObj();
 
                 mActiveDrmScheme = false;
-            } catch (Exception e) {
+            } catch (IllegalStateException e) {
                 Log.w(TAG, "releaseDrm: Exception ", e);
-                throw e;
+                throw new IllegalStateException("releaseDrm: The player is not in a valid state.");
+            } catch (Exception e) {
+                Log.e(TAG, "releaseDrm: Exception ", e);
             }
         }   // synchronized
     }
@@ -4337,21 +4400,23 @@
      * 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 keySetId is the key-set identifier of the offline keys being released when keyType is
+     * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
+     * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
+     *
+     * @param initData is the container-specific initialization data when the keyType is
+     * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. 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 {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null.
      *
      * @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 keyType specifies the type of the request. The request may be to acquire
+     * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content
+     * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired
+     * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), 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.
@@ -4360,12 +4425,13 @@
      * @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)
+    public MediaDrm.KeyRequest getKeyRequest(@Nullable byte[] keySetId, @Nullable byte[] initData,
+            @Nullable String mimeType, @MediaDrm.KeyType int keyType,
+            @Nullable Map<String, String> optionalParameters)
             throws NoDrmSchemeException
     {
         Log.v(TAG, "getKeyRequest: " +
-                " scope: " + scope + " mimeType: " + mimeType +
+                " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType +
                 " keyType: " + keyType + " optionalParameters: " + optionalParameters);
 
         synchronized (mDrmLock) {
@@ -4375,20 +4441,16 @@
             }
 
             try {
-                byte[] scopeOut = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
-                                  mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
-                                  scope;          // keySetId for KEY_TYPE_RELEASE
-
-                byte[] initData = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
-                                  scope :         // initData for KEY_TYPE_STREAMING/OFFLINE
-                                  null;           // not used for KEY_TYPE_RELEASE
+                byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
+                        mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
+                        keySetId;       // keySetId for KEY_TYPE_RELEASE
 
                 HashMap<String, String> hmapOptionalParameters =
                                                 (optionalParameters != null) ?
                                                 new HashMap<String, String>(optionalParameters) :
                                                 null;
 
-                MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scopeOut, initData, mimeType,
+                MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType,
                                                               keyType, hmapOptionalParameters);
                 Log.v(TAG, "getKeyRequest:   --> request: " + request);
 
@@ -4499,8 +4561,8 @@
      * @param propertyName the property name
      *
      * Standard fields names are:
-     * {link #PROPERTY_VENDOR}, {link #PROPERTY_VERSION},
-     * {link #PROPERTY_DESCRIPTION}, {link #PROPERTY_ALGORITHMS}
+     * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
+     * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
      */
     @NonNull
     public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName)
@@ -4537,8 +4599,8 @@
      * @param value the property value
      *
      * Standard fields names are:
-     * {link #PROPERTY_VENDOR}, {link #PROPERTY_VERSION},
-     * {link #PROPERTY_DESCRIPTION}, {link #PROPERTY_ALGORITHMS}
+     * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
+     * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
      */
     public void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName,
                                      @NonNull String value)
@@ -4565,8 +4627,6 @@
     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;
@@ -4574,15 +4634,10 @@
         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) {
+        private DrmInfo(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes) {
             mapPssh = Pssh;
             supportedSchemes = SupportedSchemes;
-            mimes = Mimes;
         }
 
         private DrmInfo(Parcel parcel) {
@@ -4608,18 +4663,12 @@
                       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);
+                  " supportedDRMsCount: " + supportedDRMsCount);
         }
 
         private DrmInfo makeCopy() {
-            return new DrmInfo(this.mapPssh, this.supportedSchemes, this.mimes);
+            return new DrmInfo(this.mapPssh, this.supportedSchemes);
         }
 
         private String arrToHex(byte[] bytes) {
@@ -4714,11 +4763,22 @@
 
     /**
      * Thrown when the device requires DRM provisioning but the provisioning attempt has
-     * failed (for example: network timeout, provisioning server error).
+     * failed due to a network error (Internet reachability, timeout, etc.).
      * Extends MediaDrm.MediaDrmException
      */
-    public static final class ProvisioningErrorException extends MediaDrmException {
-        public ProvisioningErrorException(String detailMessage) {
+    public static final class ProvisioningNetworkErrorException extends MediaDrmException {
+        public ProvisioningNetworkErrorException(String detailMessage) {
+            super(detailMessage);
+        }
+    }
+
+    /**
+     * Thrown when the device requires DRM provisioning but the provisioning attempt has
+     * failed due to the provisioning server denying the request.
+     * Extends MediaDrm.MediaDrmException
+     */
+    public static final class ProvisioningServerErrorException extends MediaDrmException {
+        public ProvisioningServerErrorException(String detailMessage) {
             super(detailMessage);
         }
     }
@@ -4770,14 +4830,13 @@
 
         private UUID uuid;
         private String urlStr;
-        private byte[] response;
         private Object drmLock;
         private OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate;
         private MediaPlayer mediaPlayer;
-        private boolean succeeded;
+        private int status;
         private boolean finished;
-        public  boolean succeeded() {
-            return succeeded;
+        public  int status() {
+            return status;
         }
 
         public ProvisioningThread initialize(MediaDrm.ProvisionRequest request,
@@ -4790,12 +4849,15 @@
             urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
             this.uuid = uuid;
 
+            status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
+
             Log.v(TAG, "HandleProvisioninig: Thread is initialised url: " + urlStr);
             return this;
         }
 
         public void run() {
 
+            byte[] response = null;
             boolean provisioningSucceeded = false;
             try {
                 URL url = new URL(urlStr);
@@ -4813,11 +4875,13 @@
                     Log.v(TAG, "HandleProvisioninig: Thread run: response " +
                             response.length + " " + response);
                 } catch (Exception e) {
+                    status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
                     Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url);
                 } finally {
                     connection.disconnect();
                 }
             } catch (Exception e)   {
+                status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
                 Log.w(TAG, "HandleProvisioninig: Thread run: openConnection " + e);
             }
 
@@ -4828,12 +4892,15 @@
                             "provideProvisionResponse SUCCEEDED!");
 
                     provisioningSucceeded = true;
-                } catch (Exception e)   {
+                } catch (Exception e) {
+                    status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
                     Log.w(TAG, "HandleProvisioninig: Thread run: " +
                             "provideProvisionResponse " + e);
                 }
             }
 
+            boolean succeeded = false;
+
             // non-blocking mode needs the lock
             if (onDrmPreparedHandlerDelegate != null) {
 
@@ -4841,6 +4908,9 @@
                     // continuing with prepareDrm
                     if (provisioningSucceeded) {
                         succeeded = mediaPlayer.resumePrepareDrm(uuid);
+                        status = (succeeded) ?
+                                PREPARE_DRM_STATUS_SUCCESS :
+                                PREPARE_DRM_STATUS_PREPARATION_ERROR;
                     }
                     mediaPlayer.mDrmProvisioningInProgress = false;
                     mediaPlayer.mPrepareDrmInProgress = false;
@@ -4850,12 +4920,15 @@
                 } // synchronized
 
                 // calling the callback outside the lock
-                onDrmPreparedHandlerDelegate.notifyClient(succeeded);
+                onDrmPreparedHandlerDelegate.notifyClient(status);
             } else {   // blocking mode already has the lock
 
                 // continuing with prepareDrm
                 if (provisioningSucceeded) {
                     succeeded = mediaPlayer.resumePrepareDrm(uuid);
+                    status = (succeeded) ?
+                            PREPARE_DRM_STATUS_SUCCESS :
+                            PREPARE_DRM_STATUS_PREPARATION_ERROR;
                 }
                 mediaPlayer.mDrmProvisioningInProgress = false;
                 mediaPlayer.mPrepareDrmInProgress = false;
@@ -4869,19 +4942,19 @@
 
     }   // ProvisioningThread
 
-    private boolean HandleProvisioninig(UUID uuid)
+    private int HandleProvisioninig(UUID uuid)
     {
         // the lock is already held by the caller
 
         if (mDrmProvisioningInProgress) {
             Log.e(TAG, "HandleProvisioninig: Unexpected mDrmProvisioningInProgress");
-            return false;
+            return PREPARE_DRM_STATUS_PREPARATION_ERROR;
         }
 
         MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
         if (provReq == null) {
             Log.e(TAG, "HandleProvisioninig: getProvisionRequest returned null.");
-            return false;
+            return PREPARE_DRM_STATUS_PREPARATION_ERROR;
         }
 
         Log.v(TAG, "HandleProvisioninig provReq " +
@@ -4893,11 +4966,11 @@
         mDrmProvisioningThread = new ProvisioningThread().initialize(provReq, uuid, this);
         mDrmProvisioningThread.start();
 
-        boolean result = false;
+        int result;
 
-        // non-blocking
+        // non-blocking: this is not the final result
         if (mOnDrmPreparedHandlerDelegate != null) {
-            result = true;
+            result = PREPARE_DRM_STATUS_SUCCESS;
         } else {
             // if blocking mode, wait till provisioning is done
             try {
@@ -4905,7 +4978,7 @@
             } catch (Exception e) {
                 Log.w(TAG, "HandleProvisioninig: Thread.join Exception " + e);
             }
-            result = mDrmProvisioningThread.succeeded();
+            result = mDrmProvisioningThread.status();
             // no longer need the thread
             mDrmProvisioningThread = null;
         }