Merge "Add audio attributes to notificationchannel"
diff --git a/Android.mk b/Android.mk
index 43dbef6..e68b310 100644
--- a/Android.mk
+++ b/Android.mk
@@ -306,6 +306,7 @@
 	core/java/android/service/wallpaper/IWallpaperService.aidl \
 	core/java/android/service/chooser/IChooserTargetService.aidl \
 	core/java/android/service/chooser/IChooserTargetResult.aidl \
+	core/java/android/text/ITextClassificationService.aidl \
 	core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\
 	core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
 	core/java/android/view/accessibility/IAccessibilityManager.aidl \
diff --git a/api/current.txt b/api/current.txt
index e941819..805586e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3553,6 +3553,7 @@
     method public android.view.WindowManager getWindowManager();
     method public boolean hasWindowFocus();
     method public void invalidateOptionsMenu();
+    method public boolean isActivityTransitionRunning();
     method public boolean isChangingConfigurations();
     method public final boolean isChild();
     method public boolean isDestroyed();
@@ -22147,7 +22148,10 @@
     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 +22165,12 @@
     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 +22187,15 @@
     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 +22238,16 @@
     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 +22256,19 @@
     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 +22297,10 @@
     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();
@@ -49236,6 +49276,7 @@
     method public android.graphics.PorterDuff.Mode getSecondaryProgressTintMode();
     method public final synchronized void incrementProgressBy(int);
     method public final synchronized void incrementSecondaryProgressBy(int);
+    method public boolean isAnimating();
     method public synchronized boolean isIndeterminate();
     method public void onRestoreInstanceState(android.os.Parcelable);
     method public android.os.Parcelable onSaveInstanceState();
diff --git a/api/system-current.txt b/api/system-current.txt
index e4bcf7e..64e9571 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3675,6 +3675,7 @@
     method public android.view.WindowManager getWindowManager();
     method public boolean hasWindowFocus();
     method public void invalidateOptionsMenu();
+    method public boolean isActivityTransitionRunning();
     method public boolean isBackgroundVisibleBehind();
     method public boolean isChangingConfigurations();
     method public final boolean isChild();
@@ -23752,7 +23753,10 @@
     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;
@@ -23766,8 +23770,12 @@
     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;
@@ -23784,10 +23792,15 @@
     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);
@@ -23830,6 +23843,16 @@
     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);
   }
@@ -23838,6 +23861,19 @@
     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);
   }
@@ -23866,6 +23902,10 @@
     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();
@@ -53003,6 +53043,7 @@
     method public android.graphics.PorterDuff.Mode getSecondaryProgressTintMode();
     method public final synchronized void incrementProgressBy(int);
     method public final synchronized void incrementSecondaryProgressBy(int);
+    method public boolean isAnimating();
     method public synchronized boolean isIndeterminate();
     method public void onRestoreInstanceState(android.os.Parcelable);
     method public android.os.Parcelable onSaveInstanceState();
diff --git a/api/test-current.txt b/api/test-current.txt
index 0006da6..a32300e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3555,6 +3555,7 @@
     method public android.view.WindowManager getWindowManager();
     method public boolean hasWindowFocus();
     method public void invalidateOptionsMenu();
+    method public boolean isActivityTransitionRunning();
     method public boolean isChangingConfigurations();
     method public final boolean isChild();
     method public boolean isDestroyed();
@@ -22239,7 +22240,10 @@
     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 +22257,12 @@
     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 +22279,15 @@
     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 +22330,16 @@
     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 +22348,19 @@
     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 +22389,10 @@
     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();
@@ -49555,6 +49595,7 @@
     method public android.graphics.PorterDuff.Mode getSecondaryProgressTintMode();
     method public final synchronized void incrementProgressBy(int);
     method public final synchronized void incrementSecondaryProgressBy(int);
+    method public boolean isAnimating();
     method public synchronized boolean isIndeterminate();
     method public void onRestoreInstanceState(android.os.Parcelable);
     method public android.os.Parcelable onSaveInstanceState();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 1d84ff5..6f95309 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4326,6 +4326,18 @@
         }
     }
 
+    /**
+     * Returns whether there are any activity transitions currently running on this
+     * activity. A return value of {@code true} can mean that either an enter or
+     * exit transition is running, including whether the background of the activity
+     * is animating as a part of that transition.
+     *
+     * @return true if a transition is currently running on this activity, false otherwise.
+     */
+    public boolean isActivityTransitionRunning() {
+        return mActivityTransitionState.isTransitionRunning();
+    }
+
     private Bundle transferSpringboardActivityOptions(Bundle options) {
         if (options == null && (mWindow != null && !mWindow.isActive())) {
             final ActivityOptions activityOptions = getActivityOptions();
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index af41db0..1eaf029 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -208,6 +208,7 @@
     private ArrayList<Matrix> mSharedElementParentMatrices;
     private boolean mSharedElementTransitionComplete;
     private boolean mViewsTransitionComplete;
+    private boolean mBackgroundAnimatorComplete;
     private ArrayList<View> mStrippedTransitioningViews = new ArrayList<>();
 
     public ActivityTransitionCoordinator(Window window,
@@ -884,6 +885,10 @@
         startInputWhenTransitionsComplete();
     }
 
+    protected void backgroundAnimatorComplete() {
+        mBackgroundAnimatorComplete = true;
+    }
+
     protected void sharedElementTransitionComplete() {
         mSharedElementTransitionComplete = true;
         startInputWhenTransitionsComplete();
@@ -966,6 +971,11 @@
         }
     }
 
+    public boolean isTransitionRunning() {
+        return !(mViewsTransitionComplete && mSharedElementTransitionComplete &&
+                mBackgroundAnimatorComplete);
+    }
+
     private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
         private Rect mEpicenter;
 
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index f2616ff..b8f5a8e 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -336,6 +336,26 @@
         }
     }
 
+    public boolean isTransitionRunning() {
+        // Note that *only* enter *or* exit will be running at any given time
+        if (mEnterTransitionCoordinator != null) {
+            if (mEnterTransitionCoordinator.isTransitionRunning()) {
+                return true;
+            }
+        }
+        if (mCalledExitCoordinator != null) {
+            if (mCalledExitCoordinator.isTransitionRunning()) {
+                return true;
+            }
+        }
+        if (mReturnExitCoordinator != null) {
+            if (mReturnExitCoordinator.isTransitionRunning()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public void startExitOutTransition(Activity activity, Bundle options) {
         mEnterTransitionCoordinator = null;
         if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) ||
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index b569b79..0454acb 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -587,6 +587,7 @@
                     @Override
                     public void onAnimationEnd(Animator animation) {
                         makeOpaque();
+                        backgroundAnimatorComplete();
                     }
                 });
                 mBackgroundAnimator.start();
@@ -598,9 +599,13 @@
                         makeOpaque();
                     }
                 });
+                backgroundAnimatorComplete();
             } else {
                 makeOpaque();
+                backgroundAnimatorComplete();
             }
+        } else {
+            backgroundAnimatorComplete();
         }
     }
 
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 2672798..f04eef2 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -201,6 +201,7 @@
 
     public void startExit() {
         if (!mIsExitStarted) {
+            backgroundAnimatorComplete();
             mIsExitStarted = true;
             pauseInput();
             ViewGroup decorView = getDecor();
@@ -303,11 +304,13 @@
                             mIsBackgroundReady = true;
                             notifyComplete();
                         }
+                        backgroundAnimatorComplete();
                     }
                 });
                 mBackgroundAnimator.setDuration(getFadeDuration());
                 mBackgroundAnimator.start();
             } else {
+                backgroundAnimatorComplete();
                 mIsBackgroundReady = true;
             }
         }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4be011e..52d7386 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1177,9 +1177,9 @@
             = "android.app.action.SHOW_DEVICE_MONITORING_DIALOG";
 
     /**
-     * Broadcast Action: Sent after application delegation scopes are changed. The new list of
-     * delegation scopes will be sent in an extra identified by the {@link #EXTRA_DELEGATION_SCOPES}
-     * key.
+     * Broadcast Action: Sent after application delegation scopes are changed. The new delegation
+     * scopes will be sent in an {@code ArrayList<String>} extra identified by the
+     * {@link #EXTRA_DELEGATION_SCOPES} key.
      *
      * <p class=”note”> Note: This is a protected intent that can only be sent by the system.</p>
      */
@@ -1188,7 +1188,7 @@
             "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
 
     /**
-     * A list of Strings corresponding to the delegation scopes given to an app in the
+     * An {@code ArrayList<String>} corresponding to the delegation scopes given to an app in the
      * {@link #ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED} broadcast.
      */
     public static final String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
@@ -3669,6 +3669,11 @@
      * Granted APIs are determined by {@code scopes}, which is a list of the {@code DELEGATION_*}
      * constants.
      * <p>
+     * A broadcast with the {@link #ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED} action will be
+     * sent to the {@code delegatePackage} with its new scopes in an {@code ArrayList<String>} extra
+     * under the {@link #EXTRA_DELEGATION_SCOPES} key. The broadcast is sent with the
+     * {@link Intent#FLAG_RECEIVER_REGISTERED_ONLY} flag.
+     * <p>
      * Delegated scopes are a per-user state. The delegated access is persistent until it is later
      * cleared by calling this method with an empty {@code scopes} list or uninstalling the
      * {@code delegatePackage}.
@@ -3704,7 +3709,7 @@
      * @throws SecurityException if {@code admin} is not a device or a profile owner.
      */
      @NonNull
-     public List<String> getDelegatedScopes(@NonNull ComponentName admin,
+     public List<String> getDelegatedScopes(@Nullable ComponentName admin,
              @NonNull String delegatedPackage) {
          throwIfParentInstance("getDelegatedScopes");
          if (mService != null) {
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 885b42f3..4534767 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1184,6 +1184,25 @@
     }
 
     /**
+     * Get the end time of the latest remote device discovery process.
+     * @return the latest time that the bluetooth adapter was/will be in discovery mode,
+     * in milliseconds since the epoch.
+     * This time can be in the future if {@link #startDiscovery()} has been called recently.
+     * @hide
+     */
+    public long getDiscoveryEndMillis() {
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.getDiscoveryEndMillis();
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return -1;
+    }
+
+    /**
      * Start the remote device discovery process.
      * <p>The discovery process usually involves an inquiry scan of about 12
      * seconds, followed by a page scan of each new device to retrieve its
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 7c5458b..53fef2a 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -52,6 +52,7 @@
     boolean startDiscovery();
     boolean cancelDiscovery();
     boolean isDiscovering();
+    long getDiscoveryEndMillis();
 
     int getAdapterConnectionState();
     int getProfileConnectionState(int profile);
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 5ac33a1..fa9f394 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -352,8 +352,8 @@
         if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) {
             argsForZygote.add("--enable-safemode");
         }
-        if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
-            argsForZygote.add("--enable-debugger");
+        if ((debugFlags & Zygote.DEBUG_ENABLE_JDWP) != 0) {
+            argsForZygote.add("--enable-jdwp");
         }
         if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
             argsForZygote.add("--enable-checkjni");
@@ -367,6 +367,9 @@
         if ((debugFlags & Zygote.DEBUG_NATIVE_DEBUGGABLE) != 0) {
             argsForZygote.add("--native-debuggable");
         }
+        if ((debugFlags & Zygote.DEBUG_JAVA_DEBUGGABLE) != 0) {
+            argsForZygote.add("--java-debuggable");
+        }
         if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
             argsForZygote.add("--enable-assert");
         }
@@ -379,9 +382,6 @@
         }
         argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
 
-        //TODO optionally enable debuger
-        //argsForZygote.add("--enable-debugger");
-
         // --setgroups is a comma-separated list
         if (gids != null && gids.length > 0) {
             StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/text/ITextClassificationService.aidl b/core/java/android/text/ITextClassificationService.aidl
new file mode 100644
index 0000000..a73dbf0
--- /dev/null
+++ b/core/java/android/text/ITextClassificationService.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Interface to the text classification service, which grants access to the text classification
+ * LSTM model file.
+ * {@hide}
+ */
+interface ITextClassificationService {
+
+    /**
+     * Request a file descriptor with read-only access to the LSTM model file.
+     * This file descriptor should be closed after the client is done with it.
+     */
+    ParcelFileDescriptor getModelFileFd();
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e8535cdb..597c051 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24768,6 +24768,13 @@
     }
 
     /**
+     * @hide Binary compatibility stub. To be removed when we finalize O APIs.
+     */
+    public void setTooltip(@Nullable CharSequence tooltipText) {
+        setTooltipText(tooltipText);
+    }
+
+    /**
      * Returns the view's tooltip text.
      *
      * @return the tooltip text
@@ -24777,6 +24784,14 @@
         return mTooltipInfo != null ? mTooltipInfo.mTooltipText : null;
     }
 
+    /**
+     * @hide Binary compatibility stub. To be removed when we finalize O APIs.
+     */
+    @Nullable
+    public CharSequence getTooltip() {
+        return getTooltipText();
+    }
+
     private boolean showTooltip(int x, int y, boolean fromLongClick) {
         if (mAttachInfo == null) {
             return false;
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index c424086..0053caa 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1924,7 +1924,7 @@
     public Transition getEnterTransition() { return null; }
 
     /**
-     * Returns he Transition that will be used to move Views out of the scene when the Window is
+     * Returns the Transition that will be used to move Views out of the scene when the Window is
      * preparing to close, for example after a call to
      * {@link android.app.Activity#finishAfterTransition()}. The exiting
      * Views will be those that are regular Views or ViewGroups that have
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 46324a3..5199b26 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -232,7 +232,7 @@
                         mDecorView.getLayoutParams();
 
                 updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
-                        p.width, p.height, mAnchoredGravity));
+                        p.width, p.height, mAnchoredGravity, false));
                 update(p.x, p.y, -1, -1, true);
             }
         }
@@ -1237,7 +1237,7 @@
         preparePopup(p);
 
         final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
-                p.width, p.height, gravity);
+                p.width, p.height, gravity, mAllowScrollingAnchorParent);
         updateAboveAnchor(aboveAnchor);
         p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;
 
@@ -1529,10 +1529,12 @@
      * @param xOffset absolute horizontal offset from the left of the anchor
      * @param yOffset absolute vertical offset from the top of the anchor
      * @param gravity horizontal gravity specifying popup alignment
+     * @param allowScroll whether the anchor view's parent may be scrolled
+     *                    when the popup window doesn't fit on screen
      * @return true if the popup is translated upwards to fit on screen
      */
     private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
-            int xOffset, int yOffset, int width, int height, int gravity) {
+            int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) {
         final int anchorHeight = anchor.getHeight();
         final int anchorWidth = anchor.getWidth();
         if (mOverlapAnchor) {
@@ -1586,7 +1588,7 @@
             final int scrollY = anchor.getScrollY();
             final Rect r = new Rect(scrollX, scrollY, scrollX + width + xOffset,
                     scrollY + height + anchorHeight + yOffset);
-            if (mAllowScrollingAnchorParent && anchor.requestRectangleOnScreen(r, true)) {
+            if (allowScroll && anchor.requestRectangleOnScreen(r, true)) {
                 // Reset for the new anchor position.
                 anchor.getLocationInWindow(drawingLocation);
                 outParams.x = drawingLocation[0] + xOffset;
@@ -2182,15 +2184,19 @@
         }
 
         final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
-                width, height, gravity);
+                width, height, gravity, mAllowScrollingAnchorParent);
         updateAboveAnchor(aboveAnchor);
 
         final boolean paramsChanged = oldGravity != p.gravity || oldX != p.x || oldY != p.y
                 || oldWidth != p.width || oldHeight != p.height;
-        // If width and mWidth were both < 0 then we have a MATCH_PARENT/WRAP_CONTENT case.
-        // findDropDownPosition will have resolved this to absolute values,
-        // but we don't want to update mWidth/mHeight to these absolute values.
-        update(p.x, p.y, width < 0 ? width : p.width, height < 0 ? height : p.height, paramsChanged);
+
+        // If width and mWidth were both < 0 then we have a MATCH_PARENT or
+        // WRAP_CONTENT case. findDropDownPosition will have resolved this to
+        // absolute values, but we don't want to update mWidth/mHeight to these
+        // absolute values.
+        final int newWidth = width < 0 ? width : p.width;
+        final int newHeight = height < 0 ? height : p.height;
+        update(p.x, p.y, newWidth, newHeight, paramsChanged);
     }
 
     /**
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 266ff75..ec2adfb 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -2058,6 +2058,18 @@
     }
 
     /**
+     * Returns whether the ProgressBar is animating or not. This is essentially the same
+     * as whether the ProgressBar is {@link #isIndeterminate() indeterminate} and visible,
+     * as indeterminate ProgressBars are always animating, and non-indeterminate
+     * ProgressBars are not animating.
+     *
+     * @return true if the ProgressBar is animating, false otherwise.
+     */
+    public boolean isAnimating() {
+        return isIndeterminate() && getWindowVisibility() == VISIBLE && isShown();
+    }
+
+    /**
      * Command for sending an accessibility event.
      */
     private class AccessibilityEventSender implements Runnable {
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index e1e0a21..59416dd 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -33,7 +33,7 @@
     */
 
     /** enable debugging over JDWP */
-    public static final int DEBUG_ENABLE_DEBUGGER   = 1;
+    public static final int DEBUG_ENABLE_JDWP   = 1;
     /** enable JNI checks */
     public static final int DEBUG_ENABLE_CHECKJNI   = 1 << 1;
     /** enable Java programming language "assert" statements */
@@ -46,8 +46,10 @@
     public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 5;
     /** Always use JIT-ed code. */
     public static final int DEBUG_ALWAYS_JIT = 1 << 6;
-    /** Make the code debuggable with turning off some optimizations. */
+    /** Make the code native debuggable by turning off some optimizations. */
     public static final int DEBUG_NATIVE_DEBUGGABLE = 1 << 7;
+    /** Make the code Java debuggable by turning off some optimizations. */
+    public static final int DEBUG_JAVA_DEBUGGABLE = 1 << 8;
 
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = 0;
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 83e3cff..8fe374c 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -342,8 +342,9 @@
         int[] gids;
 
         /**
-         * From --enable-debugger, --enable-checkjni, --enable-assert,
-         * --enable-safemode, --generate-debug-info and --enable-jni-logging.
+         * From --enable-jdwp, --enable-checkjni, --enable-assert,
+         * --enable-safemode, --generate-debug-info, --enable-jni-logging,
+         * --java-debuggable, and --native-debuggable.
          */
         int debugFlags;
 
@@ -453,8 +454,8 @@
                     targetSdkVersionSpecified = true;
                     targetSdkVersion = Integer.parseInt(
                             arg.substring(arg.indexOf('=') + 1));
-                } else if (arg.equals("--enable-debugger")) {
-                    debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+                } else if (arg.equals("--enable-jdwp")) {
+                    debugFlags |= Zygote.DEBUG_ENABLE_JDWP;
                 } else if (arg.equals("--enable-safemode")) {
                     debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
                 } else if (arg.equals("--enable-checkjni")) {
@@ -465,6 +466,8 @@
                     debugFlags |= Zygote.DEBUG_ALWAYS_JIT;
                 } else if (arg.equals("--native-debuggable")) {
                     debugFlags |= Zygote.DEBUG_NATIVE_DEBUGGABLE;
+                } else if (arg.equals("--java-debuggable")) {
+                    debugFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
                 } else if (arg.equals("--enable-jni-logging")) {
                     debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING;
                 } else if (arg.equals("--enable-assert")) {
@@ -676,14 +679,14 @@
      * Applies debugger system properties to the zygote arguments.
      *
      * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
-     * the debugger state is specified via the "--enable-debugger" flag
+     * the debugger state is specified via the "--enable-jdwp" flag
      * in the spawn request.
      *
      * @param args non-null; zygote spawner args
      */
     public static void applyDebuggerSystemProperty(Arguments args) {
         if (RoSystemProperties.DEBUGGABLE) {
-            args.debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+            args.debugFlags |= Zygote.DEBUG_ENABLE_JDWP;
         }
     }
 
@@ -705,7 +708,7 @@
         int peerUid = peer.getUid();
 
         if (args.invokeWith != null && peerUid != 0 &&
-            (args.debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) == 0) {
+            (args.debugFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
             throw new ZygoteSecurityException("Peer is permitted to specify an"
                     + "explicit invoke-with wrapper command only for debuggable"
                     + "applications.");
diff --git a/core/res/res/values-mcc208-mnc10/config.xml b/core/res/res/values-mcc208-mnc10/config.xml
index d3640e5..3ed7818 100644
--- a/core/res/res/values-mcc208-mnc10/config.xml
+++ b/core/res/res/values-mcc208-mnc10/config.xml
@@ -31,28 +31,4 @@
         <item>[ApnSettingV3]INTERNET NRJ,internetnrj,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,4E</item>
     </string-array>
 
-    <string-array translatable="false" name="config_operatorConsideredNonRoaming">
-        <item>21401</item>
-        <item>21402</item>
-        <item>21403</item>
-        <item>21404</item>
-        <item>21405</item>
-        <item>21406</item>
-        <item>21407</item>
-        <item>21408</item>
-        <item>21409</item>
-        <item>21410</item>
-        <item>21411</item>
-        <item>21412</item>
-        <item>21413</item>
-        <item>21414</item>
-        <item>21415</item>
-        <item>21416</item>
-        <item>21417</item>
-        <item>21418</item>
-        <item>21419</item>
-        <item>21420</item>
-        <item>21421</item>
-    </string-array>
-
 </resources>
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 88dde53..62abdefd 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -47,6 +47,7 @@
 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 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.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 @@
     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 @@
             mTimeProvider = null;
         }
         mOnSubtitleDataListener = null;
+
+        // Modular DRM clean up
+        mOnDrmInfoHandlerDelegate = null;
+        mOnDrmPreparedHandlerDelegate = null;
+        resetDrmState();
+
         _release();
     }
 
@@ -1906,6 +1932,8 @@
             mIndexTrackPairs.clear();
             mInbandTrackIndices.clear();
         };
+
+        resetDrmState();
     }
 
     private native void _reset();
@@ -2999,6 +3027,7 @@
     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 @@
                             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 @@
 
     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 af59d81..5e8135f 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 @@
     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 @@
     ;
 }
 
+/////////////////////////////////////////////////////////////////////////////////////
+// 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 @@
     {"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
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 115c622..2fb6843 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -135,6 +135,10 @@
         mAdapter.setDiscoverableTimeout(timeout);
     }
 
+    public long getDiscoveryEndMillis() {
+        return mAdapter.getDiscoveryEndMillis();
+    }
+
     public void setName(String name) {
         mAdapter.setName(name);
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 140ae9b..7731228 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3708,7 +3708,8 @@
             }
             int debugFlags = 0;
             if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
-                debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+                debugFlags |= Zygote.DEBUG_ENABLE_JDWP;
+                debugFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
                 // Also turn on CheckJNI for debuggable apps. It's quite
                 // awkward to turn on otherwise.
                 debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
diff --git a/services/core/java/com/android/server/text/TextClassificationService.java b/services/core/java/com/android/server/text/TextClassificationService.java
new file mode 100644
index 0000000..9358238
--- /dev/null
+++ b/services/core/java/com/android/server/text/TextClassificationService.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.text;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.text.ITextClassificationService;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Text classification service.
+ * This is used to provide access to the text classification LSTM model file.
+ */
+public class TextClassificationService extends ITextClassificationService.Stub {
+
+    private static final String LOG_TAG = "TextClassificationService";
+
+    public static final class Lifecycle extends SystemService {
+
+        private TextClassificationService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+            mService = new TextClassificationService();
+        }
+
+        @Override
+        public void onStart() {
+            try {
+                publishBinderService(Context.TEXT_CLASSIFICATION_SERVICE, mService);
+            } catch (Throwable t) {
+                // Starting this service is not critical to the running of this device and should
+                // therefore not crash the device. If it fails, log the error and continue.
+                Slog.e(LOG_TAG, "Could not start the TextClassificationService.", t);
+            }
+        }
+    }
+
+    @Override
+    public synchronized ParcelFileDescriptor getModelFileFd() throws RemoteException {
+        try {
+            return ParcelFileDescriptor.open(
+                    new File("/etc/assistant/smart-selection.model"),
+                    ParcelFileDescriptor.MODE_READ_ONLY);
+        } catch (Throwable t) {
+            Slog.e(LOG_TAG, "Error retrieving an fd to the text classification model file.", t);
+            throw new RemoteException(t.getMessage());
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d91b473..3c1d274 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4896,12 +4896,13 @@
             // Notify delegate package of updates.
             final Intent intent = new Intent(
                     DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED);
-            // Only call receivers registered in the manifest (don’t wake app if not running).
+            // Only call receivers registered with Context#registerReceiver (don’t wake delegate).
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             // Limit components this intent resolves to to the delegate package.
             intent.setPackage(delegatePackage);
             // Include the list of delegated scopes as an extra.
-            intent.putExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, scopes.toArray());
+            intent.putStringArrayListExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES,
+                    (ArrayList<String>) scopes);
             // Send the broadcast.
             mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c3ef23b..712441c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -100,6 +100,7 @@
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
 import com.android.server.telecom.TelecomLoaderService;
+import com.android.server.text.TextClassificationService;
 import com.android.server.trust.TrustManagerService;
 import com.android.server.tv.TvInputManagerService;
 import com.android.server.tv.TvRemoteService;
@@ -951,6 +952,12 @@
                 traceEnd();
             }
 
+            if (!disableNonCoreServices) {
+                traceBeginAndSlog("StartTextClassificationService");
+                mSystemServiceManager.startService(TextClassificationService.Lifecycle.class);
+                traceEnd();
+            }
+
             if (!disableNetwork) {
                 traceBeginAndSlog("StartNetworkScoreService");
                 try {