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;