Added QueueState, playUri, and playFromSearch.

Added QueueState to MediaSession/MediaController
Added play(Uri) and playFromSearch(String) to MediaController.TransportControls

Change-Id: I1a8ad5c22d05015ab6ff5700dc8a758455f1d89b
diff --git a/api/current.txt b/api/current.txt
index a49e0f7..6c61d55 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16567,18 +16567,22 @@
     method public boolean dispatchMediaButtonEvent(android.view.KeyEvent);
     method public android.media.MediaMetadata getMetadata();
     method public android.media.session.PlaybackState getPlaybackState();
+    method public java.util.List<android.media.session.MediaSession.Track> getQueue();
     method public int getRatingType();
     method public android.media.session.MediaController.TransportControls getTransportControls();
     method public android.media.session.MediaController.VolumeInfo getVolumeInfo();
     method public void removeCallback(android.media.session.MediaController.Callback);
-    method public void sendControlCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+    method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
     method public void setVolumeTo(int, int);
   }
 
   public static abstract class MediaController.Callback {
     ctor public MediaController.Callback();
+    method public void onExtrasChanged(android.os.Bundle);
     method public void onMetadataChanged(android.media.MediaMetadata);
     method public void onPlaybackStateChanged(android.media.session.PlaybackState);
+    method public void onQueueChanged(java.util.List<android.media.session.MediaSession.Track>);
+    method public void onQueueTitleChanged(java.lang.CharSequence);
     method public void onSessionEvent(java.lang.String, android.os.Bundle);
     method public void onVolumeInfoChanged(android.media.session.MediaController.VolumeInfo);
   }
@@ -16587,11 +16591,14 @@
     method public void fastForward();
     method public void pause();
     method public void play();
+    method public void playFromSearch(java.lang.String, android.os.Bundle);
+    method public void playUri(android.net.Uri, android.os.Bundle);
     method public void rewind();
     method public void seekTo(long);
     method public void setRating(android.media.Rating);
     method public void skipToNext();
     method public void skipToPrevious();
+    method public void skipToTrack(long);
     method public void stop();
   }
 
@@ -16616,6 +16623,7 @@
     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 setExtras(android.os.Bundle);
     method public void setFlags(int);
     method public void setLaunchPendingIntent(android.app.PendingIntent);
     method public void setMediaRouter(android.media.routing.MediaRouter);
@@ -16623,6 +16631,8 @@
     method public void setPlaybackState(android.media.session.PlaybackState);
     method public void setPlaybackToLocal(android.media.AudioAttributes);
     method public void setPlaybackToRemote(android.media.VolumeProvider);
+    method public void setQueue(java.util.List<android.media.session.MediaSession.Track>);
+    method public void setQueueTitle(java.lang.CharSequence);
     field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
     field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
     field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
@@ -16631,7 +16641,7 @@
 
   public static abstract class MediaSession.Callback {
     ctor public MediaSession.Callback();
-    method public void onControlCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+    method public void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
     method public void onMediaButtonEvent(android.content.Intent);
   }
 
@@ -16641,16 +16651,36 @@
     field public static final android.os.Parcelable.Creator CREATOR;
   }
 
+  public static final class MediaSession.Track implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.os.Bundle getExtras();
+    method public long getId();
+    method public android.media.MediaMetadata getMetadata();
+    method public android.net.Uri getUri();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+    field public static final int UNKNOWN_ID = -1; // 0xffffffff
+  }
+
+  public static final class MediaSession.Track.Builder {
+    ctor public MediaSession.Track.Builder(android.media.MediaMetadata, long, android.net.Uri);
+    method public android.media.session.MediaSession.Track build();
+    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 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();
   }
 
@@ -16677,12 +16707,15 @@
     field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
     field public static final long ACTION_PAUSE = 2L; // 0x2L
     field public static final long ACTION_PLAY = 4L; // 0x4L
+    field public static final long ACTION_PLAY_FROM_SEARCH = 2048L; // 0x800L
     field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
+    field public static final long ACTION_PLAY_URI = 1024L; // 0x400L
     field public static final long ACTION_REWIND = 8L; // 0x8L
     field public static final long ACTION_SEEK_TO = 256L; // 0x100L
     field public static final long ACTION_SET_RATING = 128L; // 0x80L
     field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L
     field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L
+    field public static final long ACTION_SKIP_TO_TRACK = 4096L; // 0x1000L
     field public static final long ACTION_STOP = 1L; // 0x1L
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
@@ -16704,6 +16737,7 @@
     ctor public PlaybackState.Builder(android.media.session.PlaybackState);
     method public android.media.session.PlaybackState build();
     method public android.media.session.PlaybackState.Builder setActions(long);
+    method public android.media.session.PlaybackState.Builder setActiveTrack(long);
     method public android.media.session.PlaybackState.Builder setBufferPosition(long);
     method public android.media.session.PlaybackState.Builder setErrorMessage(java.lang.CharSequence);
     method public android.media.session.PlaybackState.Builder setState(int, long, float, long);
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index d87a69e..2c190b7 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -16,11 +16,13 @@
 package android.media.session;
 
 import android.content.ComponentName;
+import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
 import android.media.MediaMetadata;
 import android.media.routing.IMediaRouter;
 import android.media.session.ISessionController;
 import android.media.session.PlaybackState;
+import android.media.session.MediaSession;
 import android.os.Bundle;
 import android.os.ResultReceiver;
 
@@ -40,6 +42,9 @@
     // These commands are for the TransportPerformer
     void setMetadata(in MediaMetadata metadata);
     void setPlaybackState(in PlaybackState state);
+    void setQueue(in ParceledListSlice queue);
+    void setQueueTitle(CharSequence title);
+    void setExtras(in Bundle extras);
     void setRatingType(int type);
 
     // These commands relate to volume handling
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index 39391b6..1c0dfa3 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -17,6 +17,7 @@
 
 import android.media.Rating;
 import android.content.Intent;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.ResultReceiver;
 
@@ -29,6 +30,9 @@
 
     // These callbacks are for the TransportPerformer
     void onPlay();
+    void onPlayUri(in Uri uri, in Bundle extras);
+    void onPlayFromSearch(String query, in Bundle extras);
+    void onSkipToTrack(long id);
     void onPause();
     void onStop();
     void onNext();
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index b555220..a553802 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -16,6 +16,7 @@
 package android.media.session;
 
 import android.content.Intent;
+import android.content.pm.ParceledListSlice;
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.routing.IMediaRouterDelegate;
@@ -24,10 +25,14 @@
 import android.media.session.MediaSessionInfo;
 import android.media.session.ParcelableVolumeInfo;
 import android.media.session.PlaybackState;
+import android.media.session.MediaSession;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.ResultReceiver;
 import android.view.KeyEvent;
 
+import java.util.List;
+
 /**
  * Interface to a MediaSession in the system.
  * @hide
@@ -48,6 +53,9 @@
 
     // These commands are for the TransportControls
     void play();
+    void playUri(in Uri uri, in Bundle extras);
+    void playFromSearch(String string, in Bundle extras);
+    void skipToTrack(long id);
     void pause();
     void stop();
     void next();
@@ -58,5 +66,8 @@
     void rate(in Rating rating);
     MediaMetadata getMetadata();
     PlaybackState getPlaybackState();
+    ParceledListSlice getQueue();
+    CharSequence getQueueTitle();
+    Bundle getExtras();
     int getRatingType();
 }
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index 64d2bc7..78cd699 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -15,9 +15,11 @@
 
 package android.media.session;
 
+import android.content.pm.ParceledListSlice;
 import android.media.MediaMetadata;
 import android.media.session.ParcelableVolumeInfo;
 import android.media.session.PlaybackState;
+import android.media.session.MediaSession;
 import android.os.Bundle;
 
 /**
@@ -29,5 +31,8 @@
     // These callbacks are for the TransportController
     void onPlaybackStateChanged(in PlaybackState state);
     void onMetadataChanged(in MediaMetadata metadata);
+    void onQueueChanged(in ParceledListSlice queue);
+    void onQueueTitleChanged(CharSequence title);
+    void onExtrasChanged(in Bundle extras);
     void onVolumeInfoChanged(in ParcelableVolumeInfo info);
 }
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 171f4c9..b699d8b 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -18,12 +18,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+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;
 import android.media.routing.MediaRouter;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -36,6 +38,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Allows an app to interact with an ongoing media session. Media buttons and
@@ -55,6 +58,9 @@
     private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
     private static final int MSG_UPDATE_METADATA = 3;
     private static final int MSG_UPDATE_VOLUME = 4;
+    private static final int MSG_UPDATE_QUEUE = 5;
+    private static final int MSG_UPDATE_QUEUE_TITLE = 6;
+    private static final int MSG_UPDATE_EXTRAS = 7;
 
     private final ISessionController mSessionBinder;
 
@@ -162,6 +168,23 @@
     }
 
     /**
+     * Get the current play queue for this session.
+     *
+     * @return The current play queue or null.
+     */
+    public @Nullable List<MediaSession.Track> getQueue() {
+        try {
+            ParceledListSlice queue = mSessionBinder.getQueue();
+            if (queue != null) {
+                return queue.getList();
+            }
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Error calling getQueue.", e);
+        }
+        return null;
+    }
+
+    /**
      * Get the rating type supported by the session. One of:
      * <ul>
      * <li>{@link Rating#RATING_NONE}</li>
@@ -309,7 +332,7 @@
      * @param params Any parameters to include with the command
      * @param cb The callback to receive the result on
      */
-    public void sendControlCommand(@NonNull String command, @Nullable Bundle params,
+    public void sendCommand(@NonNull String command, @Nullable Bundle params,
             @Nullable ResultReceiver cb) {
         if (TextUtils.isEmpty(command)) {
             throw new IllegalArgumentException("command cannot be null or empty");
@@ -438,6 +461,34 @@
         }
 
         /**
+         * Override to handle changes to tracks in the queue.
+         *
+         * @param queue A list of tracks in the current play queue. It should include the currently
+         *              playing track as well as previous and upcoming tracks if applicable.
+         * @see MediaSession.Track
+         */
+        public void onQueueChanged(@Nullable List<MediaSession.Track> queue) {
+        }
+
+        /**
+         * Override to handle changes to the queue title.
+         *
+         * @param title The title that should be displayed along with the play queue such as
+         *              "Now Playing". May be null if there is no such title.
+         */
+        public void onQueueTitleChanged(@Nullable CharSequence title) {
+        }
+
+        /**
+         * Override to handle changes to the {@link MediaSession} extras.
+         *
+         * @param extras The extras that can include other information associated with the
+         *               {@link MediaSession}.
+         */
+        public void onExtrasChanged(@Nullable Bundle extras) {
+        }
+
+        /**
          * Override to handle changes to the volume info.
          *
          * @param info The current volume info for this session.
@@ -468,6 +519,54 @@
         }
 
         /**
+         * Request that the player start playback for a specific {@link Uri}.
+         *
+         * @param uri The uri of the requested media.
+         * @param extras Optional extras that can include extra information about the media item
+         *               to be played.
+         */
+        public void playUri(Uri uri, Bundle extras) {
+            if (uri == null) {
+                throw new IllegalArgumentException("You must specify a non-null Uri for playUri.");
+            }
+            try {
+                mSessionBinder.playUri(uri, extras);
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Error calling play(" + uri + ").", e);
+            }
+        }
+
+        /**
+         * Request that the player start playback for a specific search query.
+         *
+         * @param query The search query.
+         * @param extras Optional extras that can include extra information about the query.
+         */
+        public void playFromSearch(String query, Bundle extras) {
+            if (TextUtils.isEmpty(query)) {
+                throw new IllegalArgumentException(
+                        "You must specify a non-empty search query for playFromSearch.");
+            }
+            try {
+                mSessionBinder.playFromSearch(query, extras);
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Error calling play(" + query + ").", e);
+            }
+        }
+
+        /**
+         * Play a track with a specific id in the play queue.
+         * If you specify an id that is not in the play queue, the behavior is undefined.
+         */
+        public void skipToTrack(long id) {
+            try {
+                mSessionBinder.skipToTrack(id);
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Error calling skipToTrack(" + id + ").", e);
+            }
+        }
+
+        /**
          * Request that the player pause its playback and stay at its current
          * position.
          */
@@ -678,6 +777,31 @@
         }
 
         @Override
+        public void onQueueChanged(ParceledListSlice parceledQueue) {
+            List<MediaSession.Track> queue = parceledQueue.getList();
+            MediaController controller = mController.get();
+            if (controller != null) {
+                controller.postMessage(MSG_UPDATE_QUEUE, queue, null);
+            }
+        }
+
+        @Override
+        public void onQueueTitleChanged(CharSequence title) {
+            MediaController controller = mController.get();
+            if (controller != null) {
+                controller.postMessage(MSG_UPDATE_QUEUE_TITLE, title, null);
+            }
+        }
+
+        @Override
+        public void onExtrasChanged(Bundle extras) {
+            MediaController controller = mController.get();
+            if (controller != null) {
+                controller.postMessage(MSG_UPDATE_EXTRAS, extras, null);
+            }
+        }
+
+        @Override
         public void onVolumeInfoChanged(ParcelableVolumeInfo pvi) {
             MediaController controller = mController.get();
             if (controller != null) {
@@ -709,6 +833,15 @@
                 case MSG_UPDATE_METADATA:
                     mCallback.onMetadataChanged((MediaMetadata) msg.obj);
                     break;
+                case MSG_UPDATE_QUEUE:
+                    mCallback.onQueueChanged((List<MediaSession.Track>) msg.obj);
+                    break;
+                case MSG_UPDATE_QUEUE_TITLE:
+                    mCallback.onQueueTitleChanged((CharSequence) msg.obj);
+                    break;
+                case MSG_UPDATE_EXTRAS:
+                    mCallback.onExtrasChanged((Bundle) msg.obj);
+                    break;
                 case MSG_UPDATE_VOLUME:
                     mCallback.onVolumeInfoChanged((VolumeInfo) msg.obj);
                     break;
diff --git a/media/java/android/media/session/MediaSession.aidl b/media/java/android/media/session/MediaSession.aidl
index 4a7efc2..0ad58c4 100644
--- a/media/java/android/media/session/MediaSession.aidl
+++ b/media/java/android/media/session/MediaSession.aidl
@@ -16,3 +16,4 @@
 package android.media.session;
 
 parcelable MediaSession.Token;
+parcelable MediaSession.Track;
\ No newline at end of file
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 6662303..72384c2 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -22,15 +22,14 @@
 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;
 import android.media.routing.MediaRouter;
-import android.media.session.ISessionController;
-import android.media.session.ISession;
-import android.media.session.ISessionCallback;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -41,11 +40,8 @@
 import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.Log;
 
-import com.android.internal.telephony.DctConstants.Activity;
-
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
@@ -474,6 +470,54 @@
     }
 
     /**
+     * Update the list of tracks in the play queue. It is an ordered list and should contain the
+     * current track, and previous or upcoming tracks if they exist.
+     * Specify null if there is no current play queue.
+     * <p>
+     * The queue should be of reasonable size. If the play queue is unbounded within your
+     * app, it is better to send a reasonable amount in a sliding window instead.
+     *
+     * @param queue A list of tracks in the play queue.
+     */
+    public void setQueue(@Nullable List<Track> queue) {
+        try {
+            mBinder.setQueue(new ParceledListSlice<Track>(queue));
+        } catch (RemoteException e) {
+            Log.wtf("Dead object in setQueue.", e);
+        }
+    }
+
+    /**
+     * Set the title of the play queue. The UI should display this title along
+     * with the play queue itself.
+     * e.g. "Play Queue", "Now Playing", or an album name.
+     *
+     * @param title The title of the play queue.
+     */
+    public void setQueueTitle(@Nullable CharSequence title) {
+        try {
+            mBinder.setQueueTitle(title);
+        } catch (RemoteException e) {
+            Log.wtf("Dead object in setQueueTitle.", e);
+        }
+    }
+
+    /**
+     * Set some extras that can be associated with the {@link MediaSession}. No assumptions should be made
+     * as to how a {@link MediaController} will handle these extras.
+     * Keys should be fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
+     *
+     * @param extras The extras associated with the {@link MediaSession}.
+     */
+    public void setExtras(@Nullable Bundle extras) {
+        try {
+            mBinder.setExtras(extras);
+        } catch (RemoteException e) {
+            Log.wtf("Dead object in setExtras.", e);
+        }
+    }
+
+    /**
      * Notify the system that the remote volume changed.
      *
      * @param provider The provider that is handling volume changes.
@@ -495,6 +539,18 @@
         postToTransportCallbacks(TransportMessageHandler.MSG_PLAY);
     }
 
+    private void dispatchPlayUri(Uri uri, Bundle extras) {
+        postToTransportCallbacks(TransportMessageHandler.MSG_PLAY_URI, uri, extras);
+    }
+
+    private void dispatchPlayFromSearch(String query, Bundle extras) {
+        postToTransportCallbacks(TransportMessageHandler.MSG_PLAY_SEARCH, query, extras);
+    }
+
+    private void dispatchSkipToTrack(long id) {
+        postToTransportCallbacks(TransportMessageHandler.MSG_SKIP_TO_TRACK, id);
+    }
+
     private void dispatchPause() {
         postToTransportCallbacks(TransportMessageHandler.MSG_PAUSE);
     }
@@ -556,6 +612,14 @@
         }
     }
 
+    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);
     }
@@ -699,7 +763,7 @@
          * @param extras Optional parameters for the command, may be null.
          * @param cb A result receiver to which a result may be sent by the command, may be null.
          */
-        public void onControlCommand(@NonNull String command, @Nullable Bundle extras,
+        public void onCommand(@NonNull String command, @Nullable Bundle extras,
                 @Nullable ResultReceiver cb) {
         }
     }
@@ -717,6 +781,24 @@
         }
 
         /**
+         * Override to handle requests to play a specific {@link Uri}.
+         */
+        public void onPlayUri(Uri uri, Bundle extras) {
+        }
+
+        /**
+         * Override to handle requests to begin playback from a search query.
+         */
+        public void onPlayFromSearch(String query, Bundle extras) {
+        }
+
+        /**
+         * Override to handle requests to play a track with a given id from the play queue.
+         */
+        public void onSkipToTrack(long id) {
+        }
+
+        /**
          * Override to handle requests to pause playback.
          */
         public void onPause() {
@@ -811,6 +893,30 @@
         }
 
         @Override
+        public void onPlayUri(Uri uri, Bundle extras) {
+            MediaSession session = mMediaSession.get();
+            if (session != null) {
+                session.dispatchPlayUri(uri, extras);
+            }
+        }
+
+        @Override
+        public void onPlayFromSearch(String query, Bundle extras) {
+            MediaSession session = mMediaSession.get();
+            if (session != null) {
+                session.dispatchPlayFromSearch(query, extras);
+            }
+        }
+
+        @Override
+        public void onSkipToTrack(long id) {
+            MediaSession session = mMediaSession.get();
+            if (session != null) {
+                session.dispatchSkipToTrack(id);
+            }
+        }
+
+        @Override
         public void onPause() {
             MediaSession session = mMediaSession.get();
             if (session != null) {
@@ -896,6 +1002,157 @@
 
     }
 
+    /**
+     * A single track that is part of the play queue. It contains information necessary to display
+     * a single track in the queue.
+     */
+    public static final class Track implements Parcelable {
+        /**
+         * This id is reserved. No tracks can be explicitly asigned this id.
+         */
+        public static final int UNKNOWN_ID = -1;
+
+        private final MediaMetadata mMetadata;
+        private final long mId;
+        private final Uri mUri;
+        private final Bundle mExtras;
+
+        /**
+         * Create a new {@link MediaSession.Track}.
+         *
+         * @param metadata The metadata for this track.
+         * @param id An identifier for this track. It must be unique within the play queue.
+         * @param uri The uri for this track.
+         * @param extras A bundle of extras that can be used to add extra information about the
+         *               track.
+         */
+        private Track(MediaMetadata metadata, long id, Uri uri, Bundle extras) {
+            mMetadata = metadata;
+            mId = id;
+            mUri = uri;
+            mExtras = extras;
+        }
+
+        private Track(Parcel in) {
+            mMetadata = MediaMetadata.CREATOR.createFromParcel(in);
+            mId = in.readLong();
+            mUri = Uri.CREATOR.createFromParcel(in);
+            mExtras = in.readBundle();
+        }
+
+        /**
+         * Get the metadata for this track.
+         */
+        public MediaMetadata getMetadata() {
+            return mMetadata;
+        }
+
+        /**
+         * Get the id for this track.
+         */
+        public long getId() {
+            return mId;
+        }
+
+        /**
+         * Get the Uri for this track.
+         */
+        public Uri getUri() {
+            return mUri;
+        }
+
+        /**
+         * Get the extras for this track.
+         */
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * Builder for {@link MediaSession.Track} objects.
+         */
+        public static final class Builder {
+            private final MediaMetadata mMetadata;
+            private final long mId;
+            private final Uri mUri;
+
+            private Bundle mExtras;
+
+            /**
+             * Create a builder with the metadata, id, and uri already set.
+             */
+            public Builder(MediaMetadata metadata, long id, Uri uri) {
+                if (metadata == null) {
+                    throw new IllegalArgumentException(
+                            "You must specify a non-null MediaMetadata to build a Track.");
+                }
+                if (uri == null) {
+                    throw new IllegalArgumentException(
+                            "You must specify a non-null Uri to build a Track.");
+                }
+                if (id == UNKNOWN_ID) {
+                    throw new IllegalArgumentException(
+                            "You must specify an id other than UNKNOWN_ID to build a Track.");
+                }
+                mMetadata = metadata;
+                mId = id;
+                mUri = uri;
+            }
+
+            /**
+             * Set optional extras for the track.
+             */
+            public MediaSession.Track.Builder setExtras(Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
+             * Create the {@link Track}.
+             */
+            public MediaSession.Track build() {
+                return new MediaSession.Track(mMetadata, mId, mUri, mExtras);
+            }
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            mMetadata.writeToParcel(dest, flags);
+            dest.writeLong(mId);
+            mUri.writeToParcel(dest, flags);
+            dest.writeBundle(mExtras);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Creator<MediaSession.Track> CREATOR
+                = new Creator<MediaSession.Track>() {
+
+            @Override
+            public MediaSession.Track createFromParcel(Parcel p) {
+                return new MediaSession.Track(p);
+            }
+
+            @Override
+            public MediaSession.Track[] newArray(int size) {
+                return new MediaSession.Track[size];
+            }
+        };
+
+        @Override
+        public String toString() {
+            return "MediaSession.Track {" +
+                    "Metadata=" + mMetadata +
+                    ", Id=" + mId +
+                    ", Uri=" + mUri +
+                    ", Extras=" + mExtras +
+                    " }";
+        }
+    }
+
     private class CallbackMessageHandler extends Handler {
         private static final int MSG_MEDIA_BUTTON = 1;
         private static final int MSG_COMMAND = 2;
@@ -919,7 +1176,7 @@
                         break;
                     case MSG_COMMAND:
                         Command cmd = (Command) msg.obj;
-                        mCallback.onControlCommand(cmd.command, cmd.extras, cmd.stub);
+                        mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
                         break;
                 }
             }
@@ -948,14 +1205,17 @@
 
     private class TransportMessageHandler extends Handler {
         private static final int MSG_PLAY = 1;
-        private static final int MSG_PAUSE = 2;
-        private static final int MSG_STOP = 3;
-        private static final int MSG_NEXT = 4;
-        private static final int MSG_PREVIOUS = 5;
-        private static final int MSG_FAST_FORWARD = 6;
-        private static final int MSG_REWIND = 7;
-        private static final int MSG_SEEK_TO = 8;
-        private static final int MSG_RATE = 9;
+        private static final int MSG_PLAY_URI = 2;
+        private static final int MSG_PLAY_SEARCH = 3;
+        private static final int MSG_SKIP_TO_TRACK = 4;
+        private static final int MSG_PAUSE = 5;
+        private static final int MSG_STOP = 6;
+        private static final int MSG_NEXT = 7;
+        private static final int MSG_PREVIOUS = 8;
+        private static final int MSG_FAST_FORWARD = 9;
+        private static final int MSG_REWIND = 10;
+        private static final int MSG_SEEK_TO = 11;
+        private static final int MSG_RATE = 12;
 
         private TransportControlsCallback mCallback;
 
@@ -964,6 +1224,12 @@
             mCallback = cb;
         }
 
+        public void post(int what, Object obj, Bundle bundle) {
+            Message msg = obtainMessage(what, obj);
+            msg.setData(bundle);
+            msg.sendToTarget();
+        }
+
         public void post(int what, Object obj) {
             obtainMessage(what, obj).sendToTarget();
         }
@@ -978,6 +1244,14 @@
                 case MSG_PLAY:
                     mCallback.onPlay();
                     break;
+                case MSG_PLAY_URI:
+                    mCallback.onPlayUri((Uri) msg.obj, msg.getData());
+                    break;
+                case MSG_PLAY_SEARCH:
+                    mCallback.onPlayFromSearch((String) msg.obj, msg.getData());
+                    break;
+                case MSG_SKIP_TO_TRACK:
+                    mCallback.onSkipToTrack((Long) msg.obj);
                 case MSG_PAUSE:
                     mCallback.onPause();
                     break;
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index f7e7176..3ce92cf 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.util.Log;
 
 /**
  * Playback state for a {@link MediaSession}. This includes a state like
@@ -26,6 +27,8 @@
  * and the current control capabilities.
  */
 public final class PlaybackState implements Parcelable {
+    private static final String TAG = "PlaybackState";
+
     /**
      * Indicates this performer supports the stop command.
      *
@@ -97,6 +100,27 @@
     public static final long ACTION_PLAY_PAUSE = 1 << 9;
 
     /**
+     * Indicates this performer supports the play from uri command.
+     *
+     * @see Builder#setActions(long)
+     */
+    public static final long ACTION_PLAY_URI = 1 << 10;
+
+    /**
+     * Indicates this performer supports the play from search command.
+     *
+     * @see Builder#setActions(long)
+     */
+    public static final long ACTION_PLAY_FROM_SEARCH = 1 << 11;
+
+    /**
+     * Indicates this performer supports the skip to track command.
+     *
+     * @see Builder#setActions(long)
+     */
+    public static final long ACTION_SKIP_TO_TRACK = 1 << 12;
+
+    /**
      * This is the default playback state and indicates that no media has been
      * added yet, or the performer has been reset and has no content to play.
      *
@@ -191,15 +215,17 @@
     private final long mActions;
     private final CharSequence mErrorMessage;
     private final long mUpdateTime;
+    private final long mActiveTrackId;
 
     private PlaybackState(int state, long position, long updateTime, float speed,
-            long bufferPosition, long actions, CharSequence error) {
+            long bufferPosition, long actions, long activeTrackId, CharSequence error) {
         mState = state;
         mPosition = position;
         mSpeed = speed;
         mUpdateTime = updateTime;
         mBufferPosition = bufferPosition;
         mActions = actions;
+        mActiveTrackId = activeTrackId;
         mErrorMessage = error;
     }
 
@@ -210,6 +236,7 @@
         mUpdateTime = in.readLong();
         mBufferPosition = in.readLong();
         mActions = in.readLong();
+        mActiveTrackId = in.readLong();
         mErrorMessage = in.readCharSequence();
 
     }
@@ -223,6 +250,7 @@
         bob.append(", speed=").append(mSpeed);
         bob.append(", updated=").append(mUpdateTime);
         bob.append(", actions=").append(mActions);
+        bob.append(", active track id=").append(mActiveTrackId);
         bob.append(", error=").append(mErrorMessage);
         bob.append("}");
         return bob.toString();
@@ -241,6 +269,7 @@
         dest.writeLong(mUpdateTime);
         dest.writeLong(mBufferPosition);
         dest.writeLong(mActions);
+        dest.writeLong(mActiveTrackId);
         dest.writeCharSequence(mErrorMessage);
     }
 
@@ -502,6 +531,7 @@
         private long mActions;
         private CharSequence mErrorMessage;
         private long mUpdateTime;
+        private long mActiveTrackId = MediaSession.Track.UNKNOWN_ID;
 
         /**
          * Creates an initially empty state builder.
@@ -526,6 +556,7 @@
             mActions = from.mActions;
             mErrorMessage = from.mErrorMessage;
             mUpdateTime = from.mUpdateTime;
+            mActiveTrackId = from.mActiveTrackId;
         }
 
         /**
@@ -640,6 +671,18 @@
         }
 
         /**
+         * Set the active track in the play queue by specifying its id.
+         * The default value is {@link MediaSession.Track#UNKNOWN_ID}
+         *
+         * @param id The id of the active track.
+         * @return this
+         */
+        public Builder setActiveTrack(long id) {
+            mActiveTrackId = id;
+            return this;
+        }
+
+        /**
          * Set a user readable error message. This should be set when the state
          * is {@link PlaybackState#STATE_ERROR}.
          *
@@ -658,7 +701,7 @@
          */
         public PlaybackState build() {
             return new PlaybackState(mState, mPosition, mUpdateTime, mSpeed, mBufferPosition,
-                    mActions, mErrorMessage);
+                    mActions, mActiveTrackId, mErrorMessage);
         }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 4304b59..9364fa9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -19,23 +19,29 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.media.AudioManager;
+import android.media.MediaMetadata;
+import android.media.Rating;
+import android.media.VolumeProvider;
 import android.media.routing.IMediaRouter;
 import android.media.routing.IMediaRouterDelegate;
 import android.media.routing.IMediaRouterStateCallback;
-import android.media.session.ISessionController;
-import android.media.session.ISessionControllerCallback;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
+import android.media.session.ISessionController;
+import android.media.session.ISessionControllerCallback;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionInfo;
-import android.media.session.PlaybackState;
 import android.media.session.ParcelableVolumeInfo;
+import android.media.session.PlaybackState;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.VolumeProvider;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.DeadObjectException;
@@ -46,15 +52,12 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
-import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 import android.view.KeyEvent;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.UUID;
 
 /**
@@ -99,8 +102,11 @@
 
     // TransportPerformer fields
 
+    private Bundle mExtras;
     private MediaMetadata mMetadata;
     private PlaybackState mPlaybackState;
+    private ParceledListSlice mQueue;
+    private CharSequence mQueueTitle;
     private int mRatingType;
     private long mLastActiveTime;
     // End TransportPerformer fields
@@ -452,6 +458,63 @@
         }
     }
 
+    private void pushQueueUpdate() {
+        synchronized (mLock) {
+            if (mDestroyed) {
+                return;
+            }
+            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
+                ISessionControllerCallback cb = mControllerCallbacks.get(i);
+                try {
+                    cb.onQueueChanged(mQueue);
+                } catch (DeadObjectException e) {
+                    mControllerCallbacks.remove(i);
+                    Log.w(TAG, "Removed dead callback in pushQueueUpdate.", e);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "unexpected exception in pushQueueUpdate.", e);
+                }
+            }
+        }
+    }
+
+    private void pushQueueTitleUpdate() {
+        synchronized (mLock) {
+            if (mDestroyed) {
+                return;
+            }
+            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
+                ISessionControllerCallback cb = mControllerCallbacks.get(i);
+                try {
+                    cb.onQueueTitleChanged(mQueueTitle);
+                } catch (DeadObjectException e) {
+                    mControllerCallbacks.remove(i);
+                    Log.w(TAG, "Removed dead callback in pushQueueTitleUpdate.", e);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "unexpected exception in pushQueueTitleUpdate.", e);
+                }
+            }
+        }
+    }
+
+    private void pushExtrasUpdate() {
+        synchronized (mLock) {
+            if (mDestroyed) {
+                return;
+            }
+            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
+                ISessionControllerCallback cb = mControllerCallbacks.get(i);
+                try {
+                    cb.onExtrasChanged(mExtras);
+                } catch (DeadObjectException e) {
+                    mControllerCallbacks.remove(i);
+                    Log.w(TAG, "Removed dead callback in pushExtrasUpdate.", e);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "unexpected exception in pushExtrasUpdate.", e);
+                }
+            }
+        }
+    }
+
     private void pushVolumeUpdate() {
         synchronized (mLock) {
             if (mDestroyed) {
@@ -606,6 +669,24 @@
         }
 
         @Override
+        public void setQueue(ParceledListSlice queue) {
+            mQueue = queue;
+            mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
+        }
+
+        @Override
+        public void setQueueTitle(CharSequence title) {
+            mQueueTitle = title;
+            mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE);
+        }
+
+        @Override
+        public void setExtras(Bundle extras) {
+            mExtras = extras;
+            mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS);
+        }
+
+        @Override
         public void setRatingType(int type) {
             mRatingType = type;
         }
@@ -683,6 +764,30 @@
             }
         }
 
+        public void playUri(Uri uri, Bundle extras) {
+            try {
+                mCb.onPlayUri(uri, extras);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote failure in playUri.", e);
+            }
+        }
+
+        public void playFromSearch(String query, Bundle extras) {
+            try {
+                mCb.onPlayFromSearch(query, extras);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote failure in playFromSearch.", e);
+            }
+        }
+
+        public void skipToTrack(long id) {
+            try {
+                mCb.onSkipToTrack(id);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote failure in skipToTrack", e);
+            }
+        }
+
         public void pause() {
             try {
                 mCb.onPause();
@@ -859,6 +964,22 @@
         }
 
         @Override
+        public void playUri(Uri uri, Bundle extras) throws RemoteException {
+            mSessionCb.playUri(uri, extras);
+        }
+
+        @Override
+        public void playFromSearch(String query, Bundle extras) throws RemoteException {
+            mSessionCb.playFromSearch(query, extras);
+        }
+
+        @Override
+        public void skipToTrack(long id) {
+            mSessionCb.skipToTrack(id);
+        }
+
+
+        @Override
         public void pause() throws RemoteException {
             mSessionCb.pause();
         }
@@ -910,6 +1031,21 @@
         }
 
         @Override
+        public ParceledListSlice getQueue() {
+            return mQueue;
+        }
+
+        @Override
+        public CharSequence getQueueTitle() {
+            return mQueueTitle;
+        }
+
+        @Override
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        @Override
         public int getRatingType() {
             return mRatingType;
         }
@@ -930,9 +1066,12 @@
     private class MessageHandler extends Handler {
         private static final int MSG_UPDATE_METADATA = 1;
         private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
-        private static final int MSG_SEND_EVENT = 3;
-        private static final int MSG_UPDATE_SESSION_STATE = 4;
-        private static final int MSG_UPDATE_VOLUME = 5;
+        private static final int MSG_UPDATE_QUEUE = 3;
+        private static final int MSG_UPDATE_QUEUE_TITLE = 4;
+        private static final int MSG_UPDATE_EXTRAS = 5;
+        private static final int MSG_SEND_EVENT = 6;
+        private static final int MSG_UPDATE_SESSION_STATE = 7;
+        private static final int MSG_UPDATE_VOLUME = 8;
 
         public MessageHandler(Looper looper) {
             super(looper);
@@ -946,6 +1085,15 @@
                 case MSG_UPDATE_PLAYBACK_STATE:
                     pushPlaybackStateUpdate();
                     break;
+                case MSG_UPDATE_QUEUE:
+                    pushQueueUpdate();
+                    break;
+                case MSG_UPDATE_QUEUE_TITLE:
+                    pushQueueTitleUpdate();
+                    break;
+                case MSG_UPDATE_EXTRAS:
+                    pushExtrasUpdate();
+                    break;
                 case MSG_SEND_EVENT:
                     pushEvent((String) msg.obj, msg.getData());
                     break;