Merge "freeze screen rotation during memory and app launch test" into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index 8467400..da1ebae 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -15150,6 +15150,7 @@
     method public boolean containsKey(java.lang.String);
     method public int describeContents();
     method public android.graphics.Bitmap getBitmap(java.lang.String);
+    method public android.media.MediaMetadata.Description getDescription();
     method public long getLong(java.lang.String);
     method public android.media.Rating getRating(java.lang.String);
     method public java.lang.String getString(java.lang.String);
@@ -15197,6 +15198,14 @@
     method public android.media.MediaMetadata.Builder putText(java.lang.String, java.lang.CharSequence);
   }
 
+  public final class MediaMetadata.Description {
+    method public java.lang.CharSequence getDescription();
+    method public android.graphics.Bitmap getIcon();
+    method public android.net.Uri getIconUri();
+    method public java.lang.CharSequence getSubtitle();
+    method public java.lang.CharSequence getTitle();
+  }
+
   public abstract deprecated class MediaMetadataEditor {
     method public synchronized void addEditableKey(int);
     method public abstract void apply();
@@ -16734,18 +16743,14 @@
 
   public final class MediaSession {
     ctor public MediaSession(android.content.Context, java.lang.String);
-    method public void addCallback(android.media.session.MediaSession.Callback);
-    method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler);
-    method public void addTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback);
-    method public void addTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback, android.os.Handler);
     method public android.media.session.MediaController getController();
     method public android.media.session.MediaSession.Token getSessionToken();
     method public boolean isActive();
     method public void release();
-    method public void removeCallback(android.media.session.MediaSession.Callback);
-    method public void removeTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback);
     method public void sendSessionEvent(java.lang.String, android.os.Bundle);
     method public void setActive(boolean);
+    method public void setCallback(android.media.session.MediaSession.Callback);
+    method public void setCallback(android.media.session.MediaSession.Callback, android.os.Handler);
     method public void setExtras(android.os.Bundle);
     method public void setFlags(int);
     method public void setLaunchActivity(android.app.PendingIntent);
@@ -16766,7 +16771,20 @@
   public static abstract class MediaSession.Callback {
     ctor public MediaSession.Callback();
     method public void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
-    method public void onMediaButtonEvent(android.content.Intent);
+    method public void onCustomAction(java.lang.String, android.os.Bundle);
+    method public void onFastForward();
+    method public boolean onMediaButtonEvent(android.content.Intent);
+    method public void onPause();
+    method public void onPlay();
+    method public void onPlayFromSearch(java.lang.String, android.os.Bundle);
+    method public void onPlayUri(android.net.Uri, android.os.Bundle);
+    method public void onRewind();
+    method public void onSeekTo(long);
+    method public void onSetRating(android.media.Rating);
+    method public void onSkipToNext();
+    method public void onSkipToPrevious();
+    method public void onSkipToTrack(long);
+    method public void onStop();
   }
 
   public static final class MediaSession.Token implements android.os.Parcelable {
@@ -16792,23 +16810,6 @@
     method public android.media.session.MediaSession.Track.Builder setExtras(android.os.Bundle);
   }
 
-  public static abstract class MediaSession.TransportControlsCallback {
-    ctor public MediaSession.TransportControlsCallback();
-    method public void onCustomAction(java.lang.String, android.os.Bundle);
-    method public void onFastForward();
-    method public void onPause();
-    method public void onPlay();
-    method public void onPlayFromSearch(java.lang.String, android.os.Bundle);
-    method public void onPlayUri(android.net.Uri, android.os.Bundle);
-    method public void onRewind();
-    method public void onSeekTo(long);
-    method public void onSetRating(android.media.Rating);
-    method public void onSkipToNext();
-    method public void onSkipToPrevious();
-    method public void onSkipToTrack(long);
-    method public void onStop();
-  }
-
   public final class MediaSessionManager {
     method public void addActiveSessionsListener(android.media.session.MediaSessionManager.SessionListener, android.content.ComponentName);
     method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName);
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 5f6fb6e..5b722e2 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -97,6 +97,11 @@
 
     private Context mContext = null;
     private int mMyUid;
+
+    // Since most Providers have only one authority, we keep both a String and a String[] to improve
+    // performance.
+    private String mAuthority;
+    private String[] mAuthorities;
     private String mReadPermission;
     private String mWritePermission;
     private PathPermission[] mPathPermissions;
@@ -193,7 +198,7 @@
         public Cursor query(String callingPkg, Uri uri, String[] projection,
                 String selection, String[] selectionArgs, String sortOrder,
                 ICancellationSignal cancellationSignal) {
-            getAndEnforceUserId(uri);
+            validateIncomingUri(uri);
             uri = getUriWithoutUserId(uri);
             if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
                 return rejectQuery(uri, projection, selection, selectionArgs, sortOrder,
@@ -211,14 +216,15 @@
 
         @Override
         public String getType(Uri uri) {
-            getAndEnforceUserId(uri);
+            validateIncomingUri(uri);
             uri = getUriWithoutUserId(uri);
             return ContentProvider.this.getType(uri);
         }
 
         @Override
         public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
-            int userId = getAndEnforceUserId(uri);
+            validateIncomingUri(uri);
+            int userId = getUserIdFromUri(uri);
             uri = getUriWithoutUserId(uri);
             if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
                 return rejectInsert(uri, initialValues);
@@ -233,7 +239,7 @@
 
         @Override
         public int bulkInsert(String callingPkg, Uri uri, ContentValues[] initialValues) {
-            getAndEnforceUserId(uri);
+            validateIncomingUri(uri);
             uri = getUriWithoutUserId(uri);
             if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
                 return 0;
@@ -254,20 +260,22 @@
             final int[] userIds = new int[numOperations];
             for (int i = 0; i < numOperations; i++) {
                 ContentProviderOperation operation = operations.get(i);
-                userIds[i] = getAndEnforceUserId(operation.getUri());
+                Uri uri = operation.getUri();
+                validateIncomingUri(uri);
+                userIds[i] = getUserIdFromUri(uri);
                 if (userIds[i] != UserHandle.USER_CURRENT) {
                     // Removing the user id from the uri.
                     operation = new ContentProviderOperation(operation, true);
                     operations.set(i, operation);
                 }
                 if (operation.isReadOperation()) {
-                    if (enforceReadPermission(callingPkg, operation.getUri())
+                    if (enforceReadPermission(callingPkg, uri)
                             != AppOpsManager.MODE_ALLOWED) {
                         throw new OperationApplicationException("App op not allowed", 0);
                     }
                 }
                 if (operation.isWriteOperation()) {
-                    if (enforceWritePermission(callingPkg, operation.getUri())
+                    if (enforceWritePermission(callingPkg, uri)
                             != AppOpsManager.MODE_ALLOWED) {
                         throw new OperationApplicationException("App op not allowed", 0);
                     }
@@ -290,7 +298,7 @@
 
         @Override
         public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) {
-            getAndEnforceUserId(uri);
+            validateIncomingUri(uri);
             uri = getUriWithoutUserId(uri);
             if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
                 return 0;
@@ -306,7 +314,7 @@
         @Override
         public int update(String callingPkg, Uri uri, ContentValues values, String selection,
                 String[] selectionArgs) {
-            getAndEnforceUserId(uri);
+            validateIncomingUri(uri);
             uri = getUriWithoutUserId(uri);
             if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
                 return 0;
@@ -323,7 +331,7 @@
         public ParcelFileDescriptor openFile(
                 String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
                 throws FileNotFoundException {
-            getAndEnforceUserId(uri);
+            validateIncomingUri(uri);
             uri = getUriWithoutUserId(uri);
             enforceFilePermission(callingPkg, uri, mode);
             final String original = setCallingPackage(callingPkg);
@@ -339,7 +347,7 @@
         public AssetFileDescriptor openAssetFile(
                 String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
                 throws FileNotFoundException {
-            getAndEnforceUserId(uri);
+            validateIncomingUri(uri);
             uri = getUriWithoutUserId(uri);
             enforceFilePermission(callingPkg, uri, mode);
             final String original = setCallingPackage(callingPkg);
@@ -363,7 +371,7 @@
 
         @Override
         public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
-            getAndEnforceUserId(uri);
+            validateIncomingUri(uri);
             uri = getUriWithoutUserId(uri);
             return ContentProvider.this.getStreamTypes(uri, mimeTypeFilter);
         }
@@ -371,7 +379,7 @@
         @Override
         public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
                 Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
-            getAndEnforceUserId(uri);
+            validateIncomingUri(uri);
             uri = getUriWithoutUserId(uri);
             enforceFilePermission(callingPkg, uri, "r");
             final String original = setCallingPackage(callingPkg);
@@ -390,7 +398,8 @@
 
         @Override
         public Uri canonicalize(String callingPkg, Uri uri) {
-            int userId = getAndEnforceUserId(uri);
+            validateIncomingUri(uri);
+            int userId = getUserIdFromUri(uri);
             uri = getUriWithoutUserId(uri);
             if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
                 return null;
@@ -405,7 +414,8 @@
 
         @Override
         public Uri uncanonicalize(String callingPkg, Uri uri) {
-            int userId = getAndEnforceUserId(uri);
+            validateIncomingUri(uri);
+            int userId = getUserIdFromUri(uri);
             uri = getUriWithoutUserId(uri);
             if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
                 return null;
@@ -620,6 +630,36 @@
     }
 
     /**
+     * Change the authorities of the ContentProvider.
+     * This is normally set for you from its manifest information when the provider is first
+     * created.
+     * @hide
+     * @param authorities the semi-colon separated authorities of the ContentProvider.
+     */
+    protected final void setAuthorities(String authorities) {
+        if (authorities.indexOf(';') == -1) {
+            mAuthority = authorities;
+            mAuthorities = null;
+        } else {
+            mAuthority = null;
+            mAuthorities = authorities.split(";");
+        }
+    }
+
+    /** @hide */
+    protected final boolean matchesOurAuthorities(String authority) {
+        if (mAuthority != null) {
+            return mAuthority.equals(authority);
+        }
+        int length = mAuthorities.length;
+        for (int i = 0; i < length; i++) {
+            if (mAuthorities[i].equals(authority)) return true;
+        }
+        return false;
+    }
+
+
+    /**
      * Change the permission required to read data from the content
      * provider.  This is normally set for you from its manifest information
      * when the provider is first created.
@@ -1634,6 +1674,7 @@
                 setWritePermission(info.writePermission);
                 setPathPermissions(info.pathPermissions);
                 mExported = info.exported;
+                setAuthorities(info.authority);
             }
             ContentProvider.this.onCreate();
         }
@@ -1727,14 +1768,25 @@
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         writer.println("nothing to dump");
     }
+
     /** @hide */
-    private int getAndEnforceUserId(Uri uri) {
-        int userId = getUserIdFromUri(uri, UserHandle.USER_CURRENT);
+    private void validateIncomingUri(Uri uri) throws SecurityException {
+        String auth = uri.getAuthority();
+        int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
         if (userId != UserHandle.USER_CURRENT && userId != mContext.getUserId()) {
             throw new SecurityException("trying to query a ContentProvider in user "
-                    + mContext.getUserId() + "with a uri belonging to user " + userId);
+                    + mContext.getUserId() + " with a uri belonging to user " + userId);
         }
-        return userId;
+        if (!matchesOurAuthorities(getAuthorityWithoutUserId(auth))) {
+            String message = "The authority of the uri " + uri + " does not match the one of the "
+                    + "contentProvider: ";
+            if (mAuthority != null) {
+                message += mAuthority;
+            } else {
+                message += mAuthorities;
+            }
+            throw new SecurityException(message);
+        }
     }
 
     /** @hide */
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 87d14b9..0ca800f 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -329,7 +329,7 @@
 
         try {
             String type = ActivityManagerNative.getDefault().getProviderMimeType(
-                    url, UserHandle.myUserId());
+                    ContentProvider.getUriWithoutUserId(url), resolveUserId(url));
             return type;
         } catch (RemoteException e) {
             // Arbitrary and not worth documenting, as Activity
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index d93ca2c..23894ee 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -175,7 +175,9 @@
         String[] nativePaths = null;
         try {
             nativePaths = getWebViewNativeLibraryPaths();
-        } catch (PackageManager.NameNotFoundException e) {
+        } catch (Throwable t) {
+            // Log and discard errors at this stage as we must not crash the system server.
+            Log.e(LOGTAG, "error preparing webview native library", t);
         }
         prepareWebViewInSystemServer(nativePaths);
     }
@@ -201,35 +203,37 @@
         String[] nativeLibs = null;
         try {
             nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths();
-        } catch (PackageManager.NameNotFoundException e) {
-        }
+            if (nativeLibs != null) {
+                long newVmSize = 0L;
 
-        if (nativeLibs != null) {
-            long newVmSize = 0L;
-
-            for (String path : nativeLibs) {
-                if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
-                if (path == null) continue;
-                File f = new File(path);
-                if (f.exists()) {
-                    long length = f.length();
-                    if (length > newVmSize) {
-                        newVmSize = length;
+                for (String path : nativeLibs) {
+                    if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
+                    if (path == null) continue;
+                    File f = new File(path);
+                    if (f.exists()) {
+                        long length = f.length();
+                        if (length > newVmSize) {
+                            newVmSize = length;
+                        }
                     }
                 }
-            }
 
-            if (DEBUG) {
-                Log.v(LOGTAG, "Based on library size, need " + newVmSize +
-                        " bytes of address space.");
+                if (DEBUG) {
+                    Log.v(LOGTAG, "Based on library size, need " + newVmSize +
+                            " bytes of address space.");
+                }
+                // The required memory can be larger than the file on disk (due to .bss), and an
+                // upgraded version of the library will likely be larger, so always attempt to
+                // reserve twice as much as we think to allow for the library to grow during this
+                // boot cycle.
+                newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
+                Log.d(LOGTAG, "Setting new address space to " + newVmSize);
+                SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
+                        Long.toString(newVmSize));
             }
-            // The required memory can be larger than the file on disk (due to .bss), and an
-            // upgraded version of the library will likely be larger, so always attempt to reserve
-            // twice as much as we think to allow for the library to grow during this boot cycle.
-            newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
-            Log.d(LOGTAG, "Setting new address space to " + newVmSize);
-            SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
-                    Long.toString(newVmSize));
+        } catch (Throwable t) {
+            // Log and discard errors at this stage as we must not crash the system server.
+            Log.e(LOGTAG, "error preparing webview native library", t);
         }
         prepareWebViewInSystemServer(nativeLibs);
     }
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index bbf6a3b..74f7a96 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -190,8 +190,8 @@
 
     private static final String[] PREFERRED_DESCRIPTION_ORDER = {
             METADATA_KEY_TITLE,
-            METADATA_KEY_ALBUM,
             METADATA_KEY_ARTIST,
+            METADATA_KEY_ALBUM,
             METADATA_KEY_ALBUM_ARTIST,
             METADATA_KEY_WRITER,
             METADATA_KEY_AUTHOR,
@@ -215,7 +215,6 @@
     private static final int METADATA_TYPE_TEXT = 1;
     private static final int METADATA_TYPE_BITMAP = 2;
     private static final int METADATA_TYPE_RATING = 3;
-    private static final int METADATA_TYPE_URI = 4;
     private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
 
     static {
@@ -236,16 +235,16 @@
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_URI);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_URI);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
         METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
-        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_URI);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
     }
 
     private static final SparseArray<String> EDITOR_KEY_MAPPING;
@@ -406,7 +405,6 @@
      * Returns a simple description of this metadata for display purposes.
      *
      * @return A simple description of this metadata.
-     * @hide
      */
     public @NonNull Description getDescription() {
         if (mDescription != null) {
@@ -673,43 +671,86 @@
 
     /**
      * A simple form of the metadata that can be used for display.
-     *
-     * @hide
      */
     public final class Description {
         /**
          * A primary title suitable for display or null.
          */
-        public final CharSequence title;
+        private final CharSequence mTitle;
         /**
          * A subtitle suitable for display or null.
          */
-        public final CharSequence subtitle;
+        private final CharSequence mSubtitle;
         /**
          * A description suitable for display or null.
          */
-        public final CharSequence description;
+        private final CharSequence mDescription;
         /**
          * A bitmap icon suitable for display or null.
          */
-        public final Bitmap icon;
+        private final Bitmap mIcon;
         /**
          * A Uri for an icon suitable for display or null.
          */
-        public final Uri iconUri;
+        private final Uri mIconUri;
+
+        /**
+         * Returns the best available title or null.
+         *
+         * @return A title or null.
+         */
+        public @Nullable CharSequence getTitle() {
+            return mTitle;
+        }
+
+        /**
+         * Returns the best available subtitle or null.
+         *
+         * @return A subtitle or null.
+         */
+        public @Nullable CharSequence getSubtitle() {
+            return mSubtitle;
+        }
+
+        /**
+         * Returns the best available description or null.
+         *
+         * @return A description or null.
+         */
+        public @Nullable CharSequence getDescription() {
+            return mDescription;
+        }
+
+        /**
+         * Returns the best available icon or null.
+         *
+         * @return An icon or null.
+         */
+        public @Nullable Bitmap getIcon() {
+            return mIcon;
+        }
+
+        /**
+         * Returns the best available icon Uri or null.
+         *
+         * @return An icon uri or null.
+         */
+        public @Nullable Uri getIconUri() {
+            return mIconUri;
+        }
 
         private Description(CharSequence title, CharSequence subtitle, CharSequence description,
                 Bitmap icon, Uri iconUri) {
-            this.title = title;
-            this.subtitle = subtitle;
-            this.description = description;
-            this.icon = icon;
-            this.iconUri = iconUri;
+            mTitle = title;
+            mSubtitle = subtitle;
+            mDescription = description;
+            mIcon = icon;
+            mIconUri = iconUri;
         }
 
         @Override
         public String toString() {
-            return title + ", " + subtitle + ", " + description;
+            return mTitle + ", " + mSubtitle + ", " + mDescription;
         }
     }
 
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 96c66c5..2a0fd83 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -970,8 +970,7 @@
     public final static int RCSE_ID_UNREGISTERED = -1;
 
     // USE_SESSIONS
-    private MediaSession.TransportControlsCallback mTransportListener
-            = new MediaSession.TransportControlsCallback() {
+    private MediaSession.Callback mTransportListener = new MediaSession.Callback() {
 
         @Override
         public void onSeekTo(long pos) {
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index e3c198e..f6e189a 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -179,7 +179,8 @@
     }
 
     /**
-     * Get the current play queue for this session.
+     * Get the current play queue for this session if one is set. If you only
+     * care about the current item {@link #getMetadata()} should be used.
      *
      * @return The current play queue or null.
      */
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index cf8e3dd..cf73c2a 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -21,12 +21,10 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.PendingIntent;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
-import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.VolumeProvider;
@@ -43,11 +41,11 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.KeyEvent;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -64,10 +62,8 @@
  * create a {@link MediaController} to interact with the session.
  * <p>
  * To receive commands, media keys, and other events a {@link Callback} must be
- * set with {@link #addCallback(Callback)} and {@link #setActive(boolean)
- * setActive(true)} must be called. To receive transport control commands a
- * {@link TransportControlsCallback} must be set with
- * {@link #addTransportControlsCallback}.
+ * set with {@link #setCallback(Callback)} and {@link #setActive(boolean)
+ * setActive(true)} must be called.
  * <p>
  * When an app is finished performing playback it must call {@link #release()}
  * to clean up the session and notify any controllers.
@@ -85,8 +81,7 @@
 
     /**
      * Set this flag on the session to indicate that it handles transport
-     * control commands through a {@link TransportControlsCallback}.
-     * The callback can be retrieved by calling {@link #addTransportControlsCallback}.
+     * control commands through its {@link Callback}.
      */
     public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
 
@@ -124,12 +119,9 @@
     private final ISession mBinder;
     private final CallbackStub mCbStub;
 
-    private final ArrayList<CallbackMessageHandler> mCallbacks
-            = new ArrayList<CallbackMessageHandler>();
-    private final ArrayList<TransportMessageHandler> mTransportCallbacks
-            = new ArrayList<TransportMessageHandler>();
-
+    private CallbackMessageHandler mCallback;
     private VolumeProvider mVolumeProvider;
+    private PlaybackState mPlaybackState;
 
     private boolean mActive = false;
 
@@ -177,30 +169,35 @@
     }
 
     /**
-     * Add a callback to receive updates on for the MediaSession. This includes
-     * media button and volume events. The caller's thread will be used to post
-     * events.
+     * Set the callback to receive updates for the MediaSession. This includes
+     * media button events and transport controls. The caller's thread will be
+     * used to post updates.
+     * <p>
+     * Set the callback to null to stop receiving updates.
      *
      * @param callback The callback object
      */
-    public void addCallback(@NonNull Callback callback) {
-        addCallback(callback, null);
+    public void setCallback(@Nullable Callback callback) {
+        setCallback(callback, null);
     }
 
     /**
-     * Add a callback to receive updates for the MediaSession. This includes
-     * media button and volume events.
+     * Set the callback to receive updates for the MediaSession. This includes
+     * media button events and transport controls.
+     * <p>
+     * Set the callback to null to stop receiving updates.
      *
      * @param callback The callback to receive updates on.
      * @param handler The handler that events should be posted on.
      */
-    public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
+    public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
         if (callback == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
+            mCallback = null;
+            return;
         }
         synchronized (mLock) {
-            if (getHandlerForCallbackLocked(callback) != null) {
-                Log.w(TAG, "Callback is already added, ignoring");
+            if (mCallback != null && mCallback.mCallback == callback) {
+                Log.w(TAG, "Tried to set same callback, ignoring");
                 return;
             }
             if (handler == null) {
@@ -208,18 +205,7 @@
             }
             CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
                     callback);
-            mCallbacks.add(msgHandler);
-        }
-    }
-
-    /**
-     * Remove a callback. It will no longer receive updates.
-     *
-     * @param callback The callback to remove.
-     */
-    public void removeCallback(@NonNull Callback callback) {
-        synchronized (mLock) {
-            removeCallbackLocked(callback);
+            mCallback = msgHandler;
         }
     }
 
@@ -421,63 +407,12 @@
     }
 
     /**
-     * Add a callback to receive transport controls on, such as play, rewind, or
-     * fast forward.
-     *
-     * @param callback The callback object
-     */
-    public void addTransportControlsCallback(@NonNull TransportControlsCallback callback) {
-        addTransportControlsCallback(callback, null);
-    }
-
-    /**
-     * Add a callback to receive transport controls on, such as play, rewind, or
-     * fast forward. The updates will be posted to the specified handler. If no
-     * handler is provided they will be posted to the caller's thread.
-     *
-     * @param callback The callback to receive updates on
-     * @param handler The handler to post the updates on
-     */
-    public void addTransportControlsCallback(@NonNull TransportControlsCallback callback,
-            @Nullable Handler handler) {
-        if (callback == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
-        }
-        synchronized (mLock) {
-            if (getTransportControlsHandlerForCallbackLocked(callback) != null) {
-                Log.w(TAG, "Callback is already added, ignoring");
-                return;
-            }
-            if (handler == null) {
-                handler = new Handler();
-            }
-            TransportMessageHandler msgHandler = new TransportMessageHandler(handler.getLooper(),
-                    callback);
-            mTransportCallbacks.add(msgHandler);
-        }
-    }
-
-    /**
-     * Stop receiving transport controls on the specified callback. If an update
-     * has already been posted you may still receive it after this call returns.
-     *
-     * @param callback The callback to stop receiving updates on
-     */
-    public void removeTransportControlsCallback(@NonNull TransportControlsCallback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
-        }
-        synchronized (mLock) {
-            removeTransportControlsCallbackLocked(callback);
-        }
-    }
-
-    /**
      * Update the current playback state.
      *
      * @param state The current state of playback
      */
     public void setPlaybackState(@Nullable PlaybackState state) {
+        mPlaybackState = state;
         try {
             mBinder.setPlaybackState(state);
         } catch (RemoteException e) {
@@ -566,138 +501,78 @@
     }
 
     private void dispatchPlay() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_PLAY);
+        postToCallback(CallbackMessageHandler.MSG_PLAY);
     }
 
     private void dispatchPlayUri(Uri uri, Bundle extras) {
-        postToTransportCallbacks(TransportMessageHandler.MSG_PLAY_URI, uri, extras);
+        postToCallback(CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
     }
 
     private void dispatchPlayFromSearch(String query, Bundle extras) {
-        postToTransportCallbacks(TransportMessageHandler.MSG_PLAY_SEARCH, query, extras);
+        postToCallback(CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
     }
 
     private void dispatchSkipToTrack(long id) {
-        postToTransportCallbacks(TransportMessageHandler.MSG_SKIP_TO_TRACK, id);
+        postToCallback(CallbackMessageHandler.MSG_SKIP_TO_TRACK, id);
     }
 
     private void dispatchPause() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_PAUSE);
+        postToCallback(CallbackMessageHandler.MSG_PAUSE);
     }
 
     private void dispatchStop() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_STOP);
+        postToCallback(CallbackMessageHandler.MSG_STOP);
     }
 
     private void dispatchNext() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_NEXT);
+        postToCallback(CallbackMessageHandler.MSG_NEXT);
     }
 
     private void dispatchPrevious() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_PREVIOUS);
+        postToCallback(CallbackMessageHandler.MSG_PREVIOUS);
     }
 
     private void dispatchFastForward() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_FAST_FORWARD);
+        postToCallback(CallbackMessageHandler.MSG_FAST_FORWARD);
     }
 
     private void dispatchRewind() {
-        postToTransportCallbacks(TransportMessageHandler.MSG_REWIND);
+        postToCallback(CallbackMessageHandler.MSG_REWIND);
     }
 
     private void dispatchSeekTo(long pos) {
-        postToTransportCallbacks(TransportMessageHandler.MSG_SEEK_TO, pos);
+        postToCallback(CallbackMessageHandler.MSG_SEEK_TO, pos);
     }
 
     private void dispatchRate(Rating rating) {
-        postToTransportCallbacks(TransportMessageHandler.MSG_RATE, rating);
+        postToCallback(CallbackMessageHandler.MSG_RATE, rating);
     }
 
     private void dispatchCustomAction(String action, Bundle args) {
-        postToTransportCallbacks(TransportMessageHandler.MSG_CUSTOM_ACTION, action, args);
+        postToCallback(CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
     }
 
-    private TransportMessageHandler getTransportControlsHandlerForCallbackLocked(
-            TransportControlsCallback callback) {
-        for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
-            TransportMessageHandler handler = mTransportCallbacks.get(i);
-            if (callback == handler.mCallback) {
-                return handler;
-            }
-        }
-        return null;
+    private void dispatchMediaButton(Intent mediaButtonIntent) {
+        postToCallback(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent);
     }
 
-    private boolean removeTransportControlsCallbackLocked(TransportControlsCallback callback) {
-        for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
-            if (callback == mTransportCallbacks.get(i).mCallback) {
-                mTransportCallbacks.remove(i);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void postToTransportCallbacks(int what, Object obj) {
-        synchronized (mLock) {
-            for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
-                mTransportCallbacks.get(i).post(what, obj);
-            }
-        }
-    }
-
-    private void postToTransportCallbacks(int what, Object obj, Bundle args) {
-        synchronized (mLock) {
-            for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
-                mTransportCallbacks.get(i).post(what, obj, args);
-            }
-        }
-    }
-
-    private void postToTransportCallbacks(int what) {
-        postToTransportCallbacks(what, null);
-    }
-
-    private CallbackMessageHandler getHandlerForCallbackLocked(Callback cb) {
-        if (cb == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
-        }
-        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-            CallbackMessageHandler handler = mCallbacks.get(i);
-            if (cb == handler.mCallback) {
-                return handler;
-            }
-        }
-        return null;
-    }
-
-    private boolean removeCallbackLocked(Callback cb) {
-        if (cb == null) {
-            throw new IllegalArgumentException("Callback cannot be null");
-        }
-        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-            CallbackMessageHandler handler = mCallbacks.get(i);
-            if (cb == handler.mCallback) {
-                mCallbacks.remove(i);
-                return true;
-            }
-        }
-        return false;
+    private void postToCallback(int what) {
+        postToCallback(what, null);
     }
 
     private void postCommand(String command, Bundle args, ResultReceiver resultCb) {
         Command cmd = new Command(command, args, resultCb);
-        synchronized (mLock) {
-            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                mCallbacks.get(i).post(CallbackMessageHandler.MSG_COMMAND, cmd);
-            }
-        }
+        postToCallback(CallbackMessageHandler.MSG_COMMAND, cmd);
     }
 
-    private void postMediaButton(Intent mediaButtonIntent) {
+    private void postToCallback(int what, Object obj) {
+        postToCallback(what, obj, null);
+    }
+
+    private void postToCallback(int what, Object obj, Bundle extras) {
         synchronized (mLock) {
-            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                mCallbacks.get(i).post(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent);
+            if (mCallback != null) {
+                mCallback.post(what, obj, extras);
             }
         }
     }
@@ -791,30 +666,16 @@
     }
 
     /**
-     * Receives generic commands or updates from controllers and the system.
-     * Callbacks may be registered using {@link #addCallback}.
+     * Receives media buttons, transport controls, and commands from controllers
+     * and the system. A callback may be set using {@link #setCallback}.
      */
     public abstract static class Callback {
+        private MediaSession mSession;
 
         public Callback() {
         }
 
         /**
-         * Called when a media button is pressed and this session has the
-         * highest priority or a controller sends a media button event to the
-         * session. TODO determine if using Intents identical to the ones
-         * RemoteControlClient receives is useful
-         * <p>
-         * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
-         * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
-         *
-         * @param mediaButtonIntent an intent containing the KeyEvent as an
-         *            extra
-         */
-        public void onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
-        }
-
-        /**
          * Called when a controller has sent a command to this session.
          * The owner of the session may handle custom commands but is not
          * required to.
@@ -826,13 +687,81 @@
         public void onCommand(@NonNull String command, @Nullable Bundle args,
                 @Nullable ResultReceiver cb) {
         }
-    }
 
-    /**
-     * Receives transport control commands. Callbacks may be registered using
-     * {@link #addTransportControlsCallback}.
-     */
-    public static abstract class TransportControlsCallback {
+        /**
+         * Called when a media button is pressed and this session has the
+         * highest priority or a controller sends a media button event to the
+         * session. The default behavior will call the relevant method if the
+         * action for it was set.
+         * <p>
+         * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
+         * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
+         *
+         * @param mediaButtonIntent an intent containing the KeyEvent as an
+         *            extra
+         */
+        public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
+            if (mSession != null
+                    && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
+                KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+                if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
+                    PlaybackState state = mSession.mPlaybackState;
+                    long validActions = state == null ? 0 : state.getActions();
+                    switch (ke.getKeyCode()) {
+                        case KeyEvent.KEYCODE_MEDIA_PLAY:
+                            if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
+                                onPlay();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_PAUSE:
+                            if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
+                                onPause();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_NEXT:
+                            if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+                                onSkipToNext();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+                            if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
+                                onSkipToPrevious();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_STOP:
+                            if ((validActions & PlaybackState.ACTION_STOP) != 0) {
+                                onStop();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+                            if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
+                                onFastForward();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_REWIND:
+                            if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
+                                onRewind();
+                            }
+                            break;
+                        case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+                        case KeyEvent.KEYCODE_HEADSETHOOK:
+                            boolean isPlaying = state == null ? false
+                                    : state.getState() == PlaybackState.STATE_PLAYING;
+                            boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+                                    | PlaybackState.ACTION_PLAY)) != 0;
+                            boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+                                    | PlaybackState.ACTION_PAUSE)) != 0;
+                            if (isPlaying && canPause) {
+                                onPause();
+                            } else if (!isPlaying && canPlay) {
+                                onPlay();
+                            }
+                            break;
+                    }
+                }
+            }
+            return false;
+        }
 
         /**
          * Override to handle requests to begin playback.
@@ -920,6 +849,10 @@
          */
         public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
         }
+
+        private void setSession(MediaSession session) {
+            mSession = session;
+        }
     }
 
     /**
@@ -946,7 +879,7 @@
             MediaSession session = mMediaSession.get();
             try {
                 if (session != null) {
-                    session.postMediaButton(mediaButtonIntent);
+                    session.dispatchMediaButton(mediaButtonIntent);
                 }
             } finally {
                 if (cb != null) {
@@ -1232,44 +1165,6 @@
         }
     }
 
-    private class CallbackMessageHandler extends Handler {
-        private static final int MSG_MEDIA_BUTTON = 1;
-        private static final int MSG_COMMAND = 2;
-
-        private MediaSession.Callback mCallback;
-
-        public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
-            super(looper, null, true);
-            mCallback = callback;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            synchronized (mLock) {
-                if (mCallback == null) {
-                    return;
-                }
-                switch (msg.what) {
-                    case MSG_MEDIA_BUTTON:
-                        mCallback.onMediaButtonEvent((Intent) msg.obj);
-                        break;
-                    case MSG_COMMAND:
-                        Command cmd = (Command) msg.obj;
-                        mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
-                        break;
-                }
-            }
-        }
-
-        public void post(int what, Object obj) {
-            obtainMessage(what, obj).sendToTarget();
-        }
-
-        public void post(int what, Object obj, int arg1) {
-            obtainMessage(what, arg1, 0, obj).sendToTarget();
-        }
-    }
-
     private static final class Command {
         public final String command;
         public final Bundle extras;
@@ -1282,7 +1177,8 @@
         }
     }
 
-    private class TransportMessageHandler extends Handler {
+    private class CallbackMessageHandler extends Handler {
+
         private static final int MSG_PLAY = 1;
         private static final int MSG_PLAY_URI = 2;
         private static final int MSG_PLAY_SEARCH = 3;
@@ -1296,12 +1192,14 @@
         private static final int MSG_SEEK_TO = 11;
         private static final int MSG_RATE = 12;
         private static final int MSG_CUSTOM_ACTION = 13;
+        private static final int MSG_MEDIA_BUTTON = 14;
+        private static final int MSG_COMMAND = 15;
 
-        private TransportControlsCallback mCallback;
+        private MediaSession.Callback mCallback;
 
-        public TransportMessageHandler(Looper looper, TransportControlsCallback cb) {
-            super(looper);
-            mCallback = cb;
+        public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
+            super(looper, null, true);
+            mCallback = callback;
         }
 
         public void post(int what, Object obj, Bundle bundle) {
@@ -1318,6 +1216,10 @@
             post(what, null);
         }
 
+        public void post(int what, Object obj, int arg1) {
+            obtainMessage(what, arg1, 0, obj).sendToTarget();
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -1359,6 +1261,13 @@
                 case MSG_CUSTOM_ACTION:
                     mCallback.onCustomAction((String) msg.obj, msg.getData());
                     break;
+                case MSG_MEDIA_BUTTON:
+                    mCallback.onMediaButtonEvent((Intent) msg.obj);
+                    break;
+                case MSG_COMMAND:
+                    Command cmd = (Command) msg.obj;
+                    mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
+                    break;
             }
         }
     }
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index f075ded..a182982 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -29,6 +29,9 @@
 import android.media.MediaMetadata;
 import android.media.MediaMetadataEditor;
 import android.media.MediaMetadataRetriever;
+import android.media.Rating;
+import android.media.RemoteControlClient;
+import android.media.RemoteControlClient.MetadataEditor;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -229,8 +232,7 @@
         }
     }
 
-    public void addRccListener(PendingIntent pi,
-            MediaSession.TransportControlsCallback listener) {
+    public void addRccListener(PendingIntent pi, MediaSession.Callback listener) {
         if (pi == null) {
             Log.w(TAG, "Pending intent was null, can't add rcc listener.");
             return;
@@ -247,10 +249,7 @@
                 // This is already the registered listener, ignore
                 return;
             }
-            // Otherwise it changed so we need to switch to the new one
-            holder.mSession.removeTransportControlsCallback(holder.mRccListener);
         }
-        holder.mSession.addTransportControlsCallback(listener, mHandler);
         holder.mRccListener = listener;
         holder.mFlags |= MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS;
         holder.mSession.setFlags(holder.mFlags);
@@ -266,7 +265,6 @@
         }
         SessionHolder holder = getHolder(pi, false);
         if (holder != null && holder.mRccListener != null) {
-            holder.mSession.removeTransportControlsCallback(holder.mRccListener);
             holder.mRccListener = null;
             holder.mFlags &= ~MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS;
             holder.mSession.setFlags(holder.mFlags);
@@ -288,8 +286,7 @@
             return;
         }
         if (holder.mMediaButtonListener != null) {
-            // Already have this listener registered, but update it anyway as
-            // the extras may have changed.
+            // Already have this listener registered
             if (DEBUG) {
                 Log.d(TAG, "addMediaButtonListener already added " + pi);
             }
@@ -300,11 +297,8 @@
         // set this flag
         holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
         holder.mSession.setFlags(holder.mFlags);
-        holder.mSession.addTransportControlsCallback(holder.mMediaButtonListener, mHandler);
-
-        holder.mMediaButtonReceiver = new MediaButtonReceiver(pi, context);
-        holder.mSession.addCallback(holder.mMediaButtonReceiver, mHandler);
         holder.mSession.setMediaButtonReceiver(pi);
+        holder.update();
         if (DEBUG) {
             Log.d(TAG, "addMediaButtonListener added " + pi);
         }
@@ -316,13 +310,10 @@
         }
         SessionHolder holder = getHolder(pi, false);
         if (holder != null && holder.mMediaButtonListener != null) {
-            holder.mSession.removeTransportControlsCallback(holder.mMediaButtonListener);
             holder.mFlags &= ~MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
             holder.mSession.setFlags(holder.mFlags);
             holder.mMediaButtonListener = null;
 
-            holder.mSession.removeCallback(holder.mMediaButtonReceiver);
-            holder.mMediaButtonReceiver = null;
             holder.update();
             if (DEBUG) {
                 Log.d(TAG, "removeMediaButtonListener removed " + pi);
@@ -387,22 +378,7 @@
         }
     }
 
-    private static final class MediaButtonReceiver extends MediaSession.Callback {
-        private final PendingIntent mPendingIntent;
-        private final Context mContext;
-
-        public MediaButtonReceiver(PendingIntent pi, Context context) {
-            mPendingIntent = pi;
-            mContext = context;
-        }
-
-        @Override
-        public void onMediaButtonEvent(Intent mediaButtonIntent) {
-            MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent);
-        }
-    }
-
-    private static final class MediaButtonListener extends MediaSession.TransportControlsCallback {
+    private static final class MediaButtonListener extends MediaSession.Callback {
         private final PendingIntent mPendingIntent;
         private final Context mContext;
 
@@ -412,6 +388,12 @@
         }
 
         @Override
+        public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+            MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent);
+            return true;
+        }
+
+        @Override
         public void onPlay() {
             sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY);
         }
@@ -468,10 +450,11 @@
         public final MediaSession mSession;
         public final PendingIntent mPi;
         public MediaButtonListener mMediaButtonListener;
-        public MediaButtonReceiver mMediaButtonReceiver;
-        public MediaSession.TransportControlsCallback mRccListener;
+        public MediaSession.Callback mRccListener;
         public int mFlags;
 
+        public SessionCallback mCb;
+
         public SessionHolder(MediaSession session, PendingIntent pi) {
             mSession = session;
             mPi = pi;
@@ -479,8 +462,87 @@
 
         public void update() {
             if (mMediaButtonListener == null && mRccListener == null) {
+                mSession.setCallback(null);
                 mSession.release();
+                mCb = null;
                 mSessions.remove(mPi);
+            } else if (mCb == null) {
+                mCb = new SessionCallback();
+                mSession.setCallback(mCb);
+            }
+        }
+
+        private class SessionCallback extends MediaSession.Callback {
+
+            @Override
+            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onMediaButtonEvent(mediaButtonIntent);
+                }
+                return true;
+            }
+
+            @Override
+            public void onPlay() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onPlay();
+                }
+            }
+
+            @Override
+            public void onPause() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onPause();
+                }
+            }
+
+            @Override
+            public void onSkipToNext() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onSkipToNext();
+                }
+            }
+
+            @Override
+            public void onSkipToPrevious() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onSkipToPrevious();
+                }
+            }
+
+            @Override
+            public void onFastForward() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onFastForward();
+                }
+            }
+
+            @Override
+            public void onRewind() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onRewind();
+                }
+            }
+
+            @Override
+            public void onStop() {
+                if (mMediaButtonListener != null) {
+                    mMediaButtonListener.onStop();
+                }
+            }
+
+            @Override
+            public void onSeekTo(long pos) {
+                if (mRccListener != null) {
+                    mRccListener.onSeekTo(pos);
+                }
+            }
+
+            @Override
+            public void onSetRating(Rating rating) {
+                if (mRccListener != null) {
+                    mRccListener.onSetRating(rating);
+                }
             }
         }
     }
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 65bd677..2ad8eae 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -845,20 +845,23 @@
         }
 
         /**
-         * Add a custom action to the playback state. Actions can be used to expose additional
-         * functionality to {@link MediaController MediaControllers} beyond what is offered by the
-         * standard transport controls.
+         * Add a custom action to the playback state. Actions can be used to
+         * expose additional functionality to {@link MediaController
+         * MediaControllers} beyond what is offered by the standard transport
+         * controls.
          * <p>
-         * e.g. start a radio station based on the current item or skip ahead by 30 seconds.
+         * e.g. start a radio station based on the current item or skip ahead by
+         * 30 seconds.
          *
-         * @param action An identifier for this action. It will be sent back to the
-         *               {@link MediaSession} through
-         *               {@link
-         *               MediaSession.TransportControlsCallback#onCustomAction(String, Bundle)}.
-         * @param name The display name for the action. If text is shown with the action or used
-         *             for accessibility, this is what should be used.
-         * @param icon The resource action of the icon that should be displayed for the action. The
-         *             resource should be in the package of the {@link MediaSession}.
+         * @param action An identifier for this action. It can be sent back to
+         *            the {@link MediaSession} through
+         *            {@link MediaController.TransportControls#sendCustomAction(String, Bundle)}.
+         * @param name The display name for the action. If text is shown with
+         *            the action or used for accessibility, this is what should
+         *            be used.
+         * @param icon The resource action of the icon that should be displayed
+         *            for the action. The resource should be in the package of
+         *            the {@link MediaSession}.
          * @return this
          */
         public Builder addCustomAction(String action, String name, int icon) {
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index ae6f5bc..97a6b83 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -53,13 +53,25 @@
 
     private static final String PATH_CHANNEL = "channel";
     private static final String PATH_PROGRAM = "program";
-    private static final String PATH_INPUT = "input";
     private static final String PATH_PASSTHROUGH = "passthrough";
 
     /**
+     * An optional query, update or delete URI parameter that allows the caller to specify TV input
+     * ID to filter channels.
+     * @hide
+     */
+    public static final String PARAM_INPUT = "input";
+
+    /**
+     * An optional query, update or delete URI parameter that allows the caller to specify channel
+     * ID to filter programs.
+     * @hide
+     */
+    public static final String PARAM_CHANNEL = "channel";
+
+    /**
      * An optional query, update or delete URI parameter that allows the caller to specify start
      * time (in milliseconds since the epoch) to filter programs.
-     *
      * @hide
      */
     public static final String PARAM_START_TIME = "start_time";
@@ -67,7 +79,6 @@
     /**
      * An optional query, update or delete URI parameter that allows the caller to specify end time
      * (in milliseconds since the epoch) to filter programs.
-     *
      * @hide
      */
     public static final String PARAM_END_TIME = "end_time";
@@ -76,7 +87,6 @@
      * A query, update or delete URI parameter that allows the caller to operate on all or
      * browsable-only channels. If set to "true", the rows that contain non-browsable channels are
      * not affected.
-     *
      * @hide
      */
     public static final String PARAM_BROWSABLE_ONLY = "browsable_only";
@@ -84,7 +94,6 @@
     /**
      * A optional query, update or delete URI parameter that allows the caller to specify canonical
      * genre to filter programs.
-     *
      * @hide
      */
     public static final String PARAM_CANONICAL_GENRE = "canonical_genre";
@@ -116,17 +125,7 @@
      */
     public static final Uri buildChannelUriForPassthroughTvInput(String inputId) {
         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
-                .appendPath(PATH_INPUT).appendPath(inputId).appendPath(PATH_CHANNEL)
-                .appendPath(PATH_PASSTHROUGH).build();
-    }
-
-    /**
-     * Returns true, if {@code channelUri} is a channel URI for a passthrough TV input.
-     * @hide
-     */
-    @SystemApi
-    public static final boolean isChannelUriForPassthroughTvInput(Uri channelUri) {
-        return channelUri.toString().endsWith(PATH_PASSTHROUGH);
+                .appendPath(PATH_PASSTHROUGH).appendPath(inputId).build();
     }
 
     /**
@@ -144,7 +143,7 @@
      * @param channelUri The URI of the channel whose logo is pointed to.
      */
     public static final Uri buildChannelLogoUri(Uri channelUri) {
-        if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+        if (!isChannelUriForTunerTvInput(channelUri)) {
             throw new IllegalArgumentException("Not a channel: " + channelUri);
         }
         return Uri.withAppendedPath(channelUri, Channels.Logo.CONTENT_DIRECTORY);
@@ -169,8 +168,8 @@
      * @hide
      */
     public static final Uri buildChannelsUriForInput(String inputId, boolean browsableOnly) {
-        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
-                .appendPath(PATH_INPUT).appendPath(inputId).appendPath(PATH_CHANNEL)
+        return Channels.CONTENT_URI.buildUpon()
+                .appendQueryParameter(PARAM_INPUT, inputId)
                 .appendQueryParameter(PARAM_BROWSABLE_ONLY, String.valueOf(browsableOnly)).build();
     }
 
@@ -194,8 +193,7 @@
 
         Uri uri;
         if (inputId == null) {
-            uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
-                    .appendPath(PATH_CHANNEL).build();
+            uri = Channels.CONTENT_URI;
         } else {
             uri = buildChannelsUriForInput(inputId, browsableOnly);
         }
@@ -217,9 +215,8 @@
      * @param channelId The ID of the channel to return programs for.
      */
     public static final Uri buildProgramsUriForChannel(long channelId) {
-        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
-                .appendPath(PATH_CHANNEL).appendPath(String.valueOf(channelId))
-                .appendPath(PATH_PROGRAM).build();
+        return Programs.CONTENT_URI.buildUpon()
+                .appendQueryParameter(PARAM_CHANNEL, String.valueOf(channelId)).build();
     }
 
     /**
@@ -228,7 +225,7 @@
      * @param channelUri The URI of the channel to return programs for.
      */
     public static final Uri buildProgramsUriForChannel(Uri channelUri) {
-        if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+        if (!isChannelUriForTunerTvInput(channelUri)) {
             throw new IllegalArgumentException("Not a channel: " + channelUri);
         }
         return buildProgramsUriForChannel(ContentUris.parseId(channelUri));
@@ -263,7 +260,7 @@
      */
     public static final Uri buildProgramsUriForChannel(Uri channelUri, long startTime,
             long endTime) {
-        if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+        if (!isChannelUriForTunerTvInput(channelUri)) {
             throw new IllegalArgumentException("Not a channel: " + channelUri);
         }
         return buildProgramsUriForChannel(ContentUris.parseId(channelUri), startTime, endTime);
@@ -279,41 +276,47 @@
         return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, watchedProgramId);
     }
 
-    /**
-     * Extracts the {@link Channels#COLUMN_INPUT_ID} from a given URI.
-     *
-     * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(String)},
-     *            {@link #buildChannelsUriForInput(String, boolean)}, or
-     *            {@link #buildChannelsUriForCanonicalGenre(String, String, boolean)}.
-     * @hide
-     */
-    public static final String getInputId(Uri channelsUri) {
-        final List<String> paths = channelsUri.getPathSegments();
-        if (paths.size() < 3) {
-            throw new IllegalArgumentException("Not channels: " + channelsUri);
-        }
-        if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(2))) {
-            throw new IllegalArgumentException("Not channels: " + channelsUri);
-        }
-        return paths.get(1);
+    private static final boolean isTvUri(Uri uri) {
+        return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
+                && AUTHORITY.equals(uri.getAuthority());
+    }
+
+    private static final boolean isTwoSegmentUriStartingWith(Uri uri, String pathSegment) {
+        List<String> pathSegments = uri.getPathSegments();
+        return pathSegments.size() == 2 && pathSegment.equals(pathSegments.get(0));
     }
 
     /**
-     * Extracts the {@link Channels#_ID} from a given URI.
-     *
-     * @param programsUri A URI constructed by {@link #buildProgramsUriForChannel(Uri)} or
-     *            {@link #buildProgramsUriForChannel(Uri, long, long)}.
+     * Returns true, if {@code uri} is a channel URI.
      * @hide
      */
-    public static final String getChannelId(Uri programsUri) {
-        final List<String> paths = programsUri.getPathSegments();
-        if (paths.size() < 3) {
-            throw new IllegalArgumentException("Not programs: " + programsUri);
-        }
-        if (!PATH_CHANNEL.equals(paths.get(0)) || !PATH_PROGRAM.equals(paths.get(2))) {
-            throw new IllegalArgumentException("Not programs: " + programsUri);
-        }
-        return paths.get(1);
+    public static final boolean isChannelUri(Uri uri) {
+        return isChannelUriForTunerTvInput(uri) || isChannelUriForPassthroughTvInput(uri);
+    }
+
+    /**
+     * Returns true, if {@code uri} is a channel URI for a tuner TV input.
+     * @hide
+     */
+    public static final boolean isChannelUriForTunerTvInput(Uri uri) {
+        return isTvUri(uri) && isTwoSegmentUriStartingWith(uri, PATH_CHANNEL);
+    }
+
+    /**
+     * Returns true, if {@code uri} is a channel URI for a passthrough TV input.
+     * @hide
+     */
+    @SystemApi
+    public static final boolean isChannelUriForPassthroughTvInput(Uri uri) {
+        return isTvUri(uri) && isTwoSegmentUriStartingWith(uri, PATH_PASSTHROUGH);
+    }
+
+    /**
+     * Returns true, if {@code uri} is a program URI.
+     * @hide
+     */
+    public static final boolean isProgramUri(Uri uri) {
+        return isTvUri(uri) && isTwoSegmentUriStartingWith(uri, PATH_PROGRAM);
     }
 
 
@@ -1104,7 +1107,6 @@
     /**
      * Column definitions for the TV programs that the user watched. Applications do not have access
      * to this table.
-     *
      * @hide
      */
     public static final class WatchedPrograms implements BaseTvColumns {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c7c7a92..4455901 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8971,8 +8971,10 @@
     }
 
     /**
-     * Allows app to retrieve the MIME type of a URI without having permission
-     * to access its content provider.
+     * Allows apps to retrieve the MIME type of a URI.
+     * If an app is in the same user as the ContentProvider, or if it is allowed to interact across
+     * users, then it does not need permission to access the ContentProvider.
+     * Either, it needs cross-user uri grants.
      *
      * CTS tests for this functionality can be run with "runtest cts-appsecurity".
      *
@@ -8981,12 +8983,22 @@
      */
     public String getProviderMimeType(Uri uri, int userId) {
         enforceNotIsolatedCaller("getProviderMimeType");
-        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, false, ALLOW_NON_FULL_IN_PROFILE, "getProviderMimeType", null);
         final String name = uri.getAuthority();
-        final long ident = Binder.clearCallingIdentity();
+        int callingUid = Binder.getCallingUid();
+        int callingPid = Binder.getCallingPid();
+        long ident = 0;
+        boolean clearedIdentity = false;
+        userId = unsafeConvertIncomingUser(userId);
+        if (UserHandle.getUserId(callingUid) != userId) {
+            if (checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
+                    callingUid, -1, true) == PackageManager.PERMISSION_GRANTED
+                    || checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
+                    callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
+                clearedIdentity = true;
+                ident = Binder.clearCallingIdentity();
+            }
+        }
         ContentProviderHolder holder = null;
-
         try {
             holder = getContentProviderExternalUnchecked(name, null, userId);
             if (holder != null) {
@@ -8996,10 +9008,17 @@
             Log.w(TAG, "Content provider dead retrieving " + uri, e);
             return null;
         } finally {
-            if (holder != null) {
-                removeContentProviderExternalUnchecked(name, null, userId);
+            // We need to clear the identity to call removeContentProviderExternalUnchecked
+            if (!clearedIdentity) {
+                ident = Binder.clearCallingIdentity();
             }
-            Binder.restoreCallingIdentity(ident);
+            try {
+                if (holder != null) {
+                    removeContentProviderExternalUnchecked(name, null, userId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
         }
 
         return null;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 44b7f01..36dec3e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4635,12 +4635,12 @@
         // 2.) we are defering a needed dexopt
         // 3.) we are skipping an unneeded dexopt
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
-        for (String path : paths) {
-            for (String dexCodeInstructionSet : dexCodeInstructionSets) {
-                if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
-                    continue;
-                }
+        for (String dexCodeInstructionSet : dexCodeInstructionSets) {
+            if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
+                continue;
+            }
 
+            for (String path : paths) {
                 try {
                     // This will return DEXOPT_NEEDED if we either cannot find any odex file for this
                     // patckage or the one we find does not match the image checksum (i.e. it was
@@ -4661,10 +4661,9 @@
                             // just result in an error again. Also, don't bother dexopting for other
                             // paths & ISAs.
                             return DEX_OPT_FAILED;
-                        } else {
-                            performedDexOpt = true;
-                            pkg.mDexOptPerformed.add(dexCodeInstructionSet);
                         }
+
+                        performedDexOpt = true;
                     } else if (!defer && isDexOptNeeded == DexFile.PATCHOAT_NEEDED) {
                         Log.i(TAG, "Running patchoat on: " + pkg.applicationInfo.packageName);
                         final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
@@ -4676,10 +4675,9 @@
                             // just result in an error again. Also, don't bother dexopting for other
                             // paths & ISAs.
                             return DEX_OPT_FAILED;
-                        } else {
-                            performedDexOpt = true;
-                            pkg.mDexOptPerformed.add(dexCodeInstructionSet);
                         }
+
+                        performedDexOpt = true;
                     }
 
                     // We're deciding to defer a needed dexopt. Don't bother dexopting for other
@@ -4706,6 +4704,13 @@
                     return DEX_OPT_FAILED;
                 }
             }
+
+            // At this point we haven't failed dexopt and we haven't deferred dexopt. We must
+            // either have either succeeded dexopt, or have had isDexOptNeededInternal tell us
+            // it isn't required. We therefore mark that this package doesn't need dexopt unless
+            // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
+            // it.
+            pkg.mDexOptPerformed.add(dexCodeInstructionSet);
         }
 
         // If we've gotten here, we're sure that no error occurred and that we haven't
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index feecfde..890d68d 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -86,10 +86,10 @@
         mRouter.setRoutingCallback(new RoutingCallback(), null);
 
         mSession = new MediaSession(mContext, "OneMedia");
-        mSession.addCallback(mCallback);
-        mSession.addTransportControlsCallback(new TransportCallback());
+        mSession.setCallback(mCallback);
         mSession.setPlaybackState(mPlaybackState);
-        mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS
+                | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
         mSession.setMediaRouter(mRouter);
         mSession.setActive(true);
     }
@@ -230,26 +230,6 @@
 
     private class SessionCb extends MediaSession.Callback {
         @Override
-        public void onMediaButtonEvent(Intent mediaRequestIntent) {
-            if (Intent.ACTION_MEDIA_BUTTON.equals(mediaRequestIntent.getAction())) {
-                KeyEvent event = (KeyEvent) mediaRequestIntent
-                        .getParcelableExtra(Intent.EXTRA_KEY_EVENT);
-                switch (event.getKeyCode()) {
-                    case KeyEvent.KEYCODE_MEDIA_PLAY:
-                        Log.d(TAG, "play button received");
-                        mRenderer.onPlay();
-                        break;
-                    case KeyEvent.KEYCODE_MEDIA_PAUSE:
-                        Log.d(TAG, "pause button received");
-                        mRenderer.onPause();
-                        break;
-                }
-            }
-        }
-    }
-
-    private class TransportCallback extends MediaSession.TransportControlsCallback {
-        @Override
         public void onPlay() {
             mRenderer.onPlay();
         }
@@ -315,7 +295,7 @@
                         updateState(PlaybackState.STATE_NONE);
                         break;
                 }
-            } 
+            }
         }
     }
 }