summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--media/java/android/media/AudioManager.java47
-rw-r--r--media/java/android/media/RemoteController.java343
-rw-r--r--media/java/android/media/session/ISessionController.aidl1
-rw-r--r--media/java/android/media/session/MediaController.java28
-rw-r--r--media/java/android/media/session/MediaSessionLegacyHelper.java86
-rw-r--r--media/java/android/media/session/MediaSessionManager.java14
-rw-r--r--media/java/android/media/session/PlaybackState.java81
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java5
8 files changed, 519 insertions, 86 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index ce9fbb18d89a..458139b14984 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2344,19 +2344,24 @@ public class AudioManager {
if (rctlr == null) {
return false;
}
- IAudioService service = getService();
- final RemoteController.OnClientUpdateListener l = rctlr.getUpdateListener();
- final ComponentName listenerComponent = new ComponentName(mContext, l.getClass());
- try {
- int[] artworkDimensions = rctlr.getArtworkSize();
- boolean reg = service.registerRemoteController(rctlr.getRcDisplay(),
- artworkDimensions[0]/*w*/, artworkDimensions[1]/*h*/,
- listenerComponent);
- rctlr.setIsRegistered(reg);
- return reg;
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in registerRemoteController " + e);
- return false;
+ if (USE_SESSIONS) {
+ rctlr.startListeningToSessions();
+ return true;
+ } else {
+ IAudioService service = getService();
+ final RemoteController.OnClientUpdateListener l = rctlr.getUpdateListener();
+ final ComponentName listenerComponent = new ComponentName(mContext, l.getClass());
+ try {
+ int[] artworkDimensions = rctlr.getArtworkSize();
+ boolean reg = service.registerRemoteController(rctlr.getRcDisplay(),
+ artworkDimensions[0]/* w */, artworkDimensions[1]/* h */,
+ listenerComponent);
+ rctlr.setIsRegistered(reg);
+ return reg;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in registerRemoteController " + e);
+ return false;
+ }
}
}
@@ -2369,12 +2374,16 @@ public class AudioManager {
if (rctlr == null) {
return;
}
- IAudioService service = getService();
- try {
- service.unregisterRemoteControlDisplay(rctlr.getRcDisplay());
- rctlr.setIsRegistered(false);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in unregisterRemoteControlDisplay " + e);
+ if (USE_SESSIONS) {
+ rctlr.stopListeningToSessions();
+ } else {
+ IAudioService service = getService();
+ try {
+ service.unregisterRemoteControlDisplay(rctlr.getRcDisplay());
+ rctlr.setIsRegistered(false);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in unregisterRemoteControlDisplay " + e);
+ }
}
}
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 37115850539b..76c72999e24f 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -19,11 +19,17 @@ package android.media;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.IRemoteControlDisplay;
import android.media.MediaMetadataEditor;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionLegacyHelper;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -34,6 +40,7 @@ import android.util.Log;
import android.view.KeyEvent;
import java.lang.ref.WeakReference;
+import java.util.List;
/**
* The RemoteController class is used to control media playback, display and update media metadata
@@ -56,6 +63,7 @@ public final class RemoteController
private final static int TRANSPORT_UNKNOWN = 0;
private final static String TAG = "RemoteController";
private final static boolean DEBUG = false;
+ private final static boolean USE_SESSIONS = true;
private final static Object mGenLock = new Object();
private final static Object mInfoLock = new Object();
private final RcDisplay mRcd;
@@ -64,6 +72,11 @@ public final class RemoteController
private final int mMaxBitmapDimension;
private MetadataEditor mMetadataEditor;
+ private MediaSessionManager mSessionManager;
+ private MediaSessionManager.SessionListener mSessionListener
+ = new TopTransportSessionListener();
+ private MediaController.Callback mSessionCb = new MediaControllerCallback();
+
/**
* Synchronized on mGenLock
*/
@@ -79,6 +92,8 @@ public final class RemoteController
private int mArtworkWidth = -1;
private int mArtworkHeight = -1;
private boolean mEnabled = true;
+ // synchronized on mInfoLock, for USE_SESSION apis.
+ private MediaController mCurrentSession;
/**
* Class constructor.
@@ -123,6 +138,8 @@ public final class RemoteController
mContext = context;
mRcd = new RcDisplay(this);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mSessionManager = (MediaSessionManager) context
+ .getSystemService(Context.MEDIA_SESSION_SERVICE);
if (ActivityManager.isLowRamDeviceStatic()) {
mMaxBitmapDimension = MAX_BITMAP_DIMENSION;
@@ -194,8 +211,15 @@ public final class RemoteController
* @hide
*/
public String getRemoteControlClientPackageName() {
- return mClientPendingIntentCurrent != null ?
- mClientPendingIntentCurrent.getCreatorPackage() : null;
+ if (USE_SESSIONS) {
+ synchronized (mInfoLock) {
+ return mCurrentSession != null ? mCurrentSession.getSessionInfo().getPackageName()
+ : null;
+ }
+ } else {
+ return mClientPendingIntentCurrent != null ?
+ mClientPendingIntentCurrent.getCreatorPackage() : null;
+ }
}
/**
@@ -215,22 +239,38 @@ public final class RemoteController
* @see OnClientUpdateListener#onClientPlaybackStateUpdate(int, long, long, float)
*/
public long getEstimatedMediaPosition() {
- if (mLastPlaybackInfo != null) {
- if (!RemoteControlClient.playbackPositionShouldMove(mLastPlaybackInfo.mState)) {
- return mLastPlaybackInfo.mCurrentPosMs;
+ if (USE_SESSIONS) {
+ synchronized (mInfoLock) {
+ if (mCurrentSession != null) {
+ PlaybackState state = mCurrentSession.getPlaybackState();
+ if (state != null) {
+ return state.getPosition();
+ }
+ }
}
-
- // Take the current position at the time of state change and estimate.
- final long thenPos = mLastPlaybackInfo.mCurrentPosMs;
- if (thenPos < 0) {
- return -1;
+ } else {
+ final PlaybackInfo lastPlaybackInfo;
+ synchronized (mInfoLock) {
+ lastPlaybackInfo = mLastPlaybackInfo;
}
+ if (lastPlaybackInfo != null) {
+ if (!RemoteControlClient.playbackPositionShouldMove(lastPlaybackInfo.mState)) {
+ return lastPlaybackInfo.mCurrentPosMs;
+ }
+
+ // Take the current position at the time of state change and
+ // estimate.
+ final long thenPos = lastPlaybackInfo.mCurrentPosMs;
+ if (thenPos < 0) {
+ return -1;
+ }
- final long now = SystemClock.elapsedRealtime();
- final long then = mLastPlaybackInfo.mStateChangeTimeMs;
- final long sinceThen = now - then;
- final long scaledSinceThen = (long) (sinceThen * mLastPlaybackInfo.mSpeed);
- return thenPos + scaledSinceThen;
+ final long now = SystemClock.elapsedRealtime();
+ final long then = lastPlaybackInfo.mStateChangeTimeMs;
+ final long sinceThen = now - then;
+ final long scaledSinceThen = (long) (sinceThen * lastPlaybackInfo.mSpeed);
+ return thenPos + scaledSinceThen;
+ }
}
return -1;
}
@@ -267,30 +307,40 @@ public final class RemoteController
if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
throw new IllegalArgumentException("not a media key event");
}
- final PendingIntent pi;
- synchronized(mInfoLock) {
- if (!mIsRegistered) {
- Log.e(TAG, "Cannot use sendMediaKeyEvent() from an unregistered RemoteController");
+ if (USE_SESSIONS) {
+ synchronized (mInfoLock) {
+ if (mCurrentSession != null) {
+ return mCurrentSession.dispatchMediaButtonEvent(keyEvent);
+ }
return false;
}
- if (!mEnabled) {
- Log.e(TAG, "Cannot use sendMediaKeyEvent() from a disabled RemoteController");
- return false;
+ } else {
+ final PendingIntent pi;
+ synchronized (mInfoLock) {
+ if (!mIsRegistered) {
+ Log.e(TAG,
+ "Cannot use sendMediaKeyEvent() from an unregistered RemoteController");
+ return false;
+ }
+ if (!mEnabled) {
+ Log.e(TAG, "Cannot use sendMediaKeyEvent() from a disabled RemoteController");
+ return false;
+ }
+ pi = mClientPendingIntentCurrent;
}
- pi = mClientPendingIntentCurrent;
- }
- if (pi != null) {
- Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- try {
- pi.send(mContext, 0, intent);
- } catch (CanceledException e) {
- Log.e(TAG, "Error sending intent for media button down: ", e);
+ if (pi != null) {
+ Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+ try {
+ pi.send(mContext, 0, intent);
+ } catch (CanceledException e) {
+ Log.e(TAG, "Error sending intent for media button down: ", e);
+ return false;
+ }
+ } else {
+ Log.i(TAG, "No-op when sending key click, no receiver right now");
return false;
}
- } else {
- Log.i(TAG, "No-op when sending key click, no receiver right now");
- return false;
}
return true;
}
@@ -311,11 +361,19 @@ public final class RemoteController
if (timeMs < 0) {
throw new IllegalArgumentException("illegal negative time value");
}
- final int genId;
- synchronized (mGenLock) {
- genId = mClientGenerationIdCurrent;
+ if (USE_SESSIONS) {
+ synchronized (mInfoLock) {
+ if (mCurrentSession != null) {
+ mCurrentSession.getTransportControls().seekTo(timeMs);
+ }
+ }
+ } else {
+ final int genId;
+ synchronized (mGenLock) {
+ genId = mClientGenerationIdCurrent;
+ }
+ mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs);
}
- mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs);
return true;
}
@@ -430,7 +488,6 @@ public final class RemoteController
return editor;
}
-
/**
* A class to read the metadata published by a {@link RemoteControlClient}, or send a
* {@link RemoteControlClient} new values for keys that can be edited.
@@ -477,26 +534,41 @@ public final class RemoteController
if (!mMetadataChanged) {
return;
}
- final int genId;
- synchronized(mGenLock) {
- genId = mClientGenerationIdCurrent;
- }
- synchronized(mInfoLock) {
- if (mEditorMetadata.containsKey(
- String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
- Rating rating = (Rating) getObject(
- MediaMetadataEditor.RATING_KEY_BY_USER, null);
- mAudioManager.updateRemoteControlClientMetadata(genId,
- MediaMetadataEditor.RATING_KEY_BY_USER,
- rating);
- } else {
- Log.e(TAG, "no metadata to apply");
+ if (USE_SESSIONS) {
+ synchronized (mInfoLock) {
+ if (mCurrentSession != null) {
+ if (mEditorMetadata.containsKey(
+ String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
+ Rating rating = (Rating) getObject(
+ MediaMetadataEditor.RATING_KEY_BY_USER, null);
+ if (rating != null) {
+ mCurrentSession.getTransportControls().setRating(rating);
+ }
+ }
+ }
+ }
+ } else {
+ final int genId;
+ synchronized(mGenLock) {
+ genId = mClientGenerationIdCurrent;
+ }
+ synchronized(mInfoLock) {
+ if (mEditorMetadata.containsKey(
+ String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
+ Rating rating = (Rating) getObject(
+ MediaMetadataEditor.RATING_KEY_BY_USER, null);
+ mAudioManager.updateRemoteControlClientMetadata(genId,
+ MediaMetadataEditor.RATING_KEY_BY_USER,
+ rating);
+ } else {
+ Log.e(TAG, "no metadata to apply");
+ }
}
- // NOT setting mApplied to true as this type of MetadataEditor will be applied
- // multiple times, whenever the user of a RemoteController needs to change the
- // metadata (e.g. user changes the rating of a song more than once during playback)
- mApplied = false;
}
+ // NOT setting mApplied to true as this type of MetadataEditor will be applied
+ // multiple times, whenever the user of a RemoteController needs to change the
+ // metadata (e.g. user changes the rating of a song more than once during playback)
+ mApplied = false;
}
}
@@ -649,6 +721,46 @@ public final class RemoteController
}
}
+ /**
+ * This receives updates when the current session changes. This is
+ * registered to receive the updates on the handler thread so it can call
+ * directly into the appropriate methods.
+ */
+ private class MediaControllerCallback extends MediaController.Callback {
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ onNewPlaybackState(state);
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ onNewMediaMetadata(metadata);
+ }
+ }
+
+ /**
+ * Listens for changes to the active session stack and replaces the
+ * currently tracked session if it has changed.
+ */
+ private class TopTransportSessionListener extends MediaSessionManager.SessionListener {
+ @Override
+ public void onActiveSessionsChanged(List<MediaController> controllers) {
+ int size = controllers.size();
+ for (int i = 0; i < size; i++) {
+ MediaController controller = controllers.get(i);
+ long flags = controller.getFlags();
+ // We only care about sessions that handle transport controls,
+ // which will be true for apps using RCC
+ if ((flags & MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
+ updateController(controller);
+ return;
+ }
+ }
+ updateController(null);
+ }
+
+ }
+
//==================================================
// Event handling
private final EventHandler mEventHandler;
@@ -658,6 +770,8 @@ public final class RemoteController
private final static int MSG_NEW_METADATA = 3; // msg always has non-null obj parameter
private final static int MSG_CLIENT_CHANGE = 4;
private final static int MSG_DISPLAY_ENABLE = 5;
+ private final static int MSG_NEW_PLAYBACK_STATE = 6;
+ private final static int MSG_NEW_MEDIA_METADATA = 7;
private class EventHandler extends Handler {
@@ -686,12 +800,46 @@ public final class RemoteController
case MSG_DISPLAY_ENABLE:
onDisplayEnable(msg.arg1 == 1);
break;
+ case MSG_NEW_PLAYBACK_STATE:
+ // same as new playback info but using new apis
+ onNewPlaybackState((PlaybackState) msg.obj);
+ break;
+ case MSG_NEW_MEDIA_METADATA:
+ onNewMediaMetadata((MediaMetadata) msg.obj);
+ break;
default:
Log.e(TAG, "unknown event " + msg.what);
}
}
}
+ /**
+ * @hide
+ */
+ void startListeningToSessions() {
+ final ComponentName listenerComponent = new ComponentName(mContext,
+ mOnClientUpdateListener.getClass());
+ mSessionManager.addActiveSessionsListener(mSessionListener, listenerComponent,
+ ActivityManager.getCurrentUser());
+ mSessionListener.onActiveSessionsChanged(mSessionManager
+ .getActiveSessions(listenerComponent));
+ if (DEBUG) {
+ Log.d(TAG, "Registered session listener with component " + listenerComponent
+ + " for user " + ActivityManager.getCurrentUser());
+ }
+ }
+
+ /**
+ * @hide
+ */
+ void stopListeningToSessions() {
+ mSessionManager.removeActiveSessionsListener(mSessionListener);
+ if (DEBUG) {
+ Log.d(TAG, "Unregistered session listener for user "
+ + ActivityManager.getCurrentUser());
+ }
+ }
+
/** If the msg is already queued, replace it with this one. */
private static final int SENDMSG_REPLACE = 0;
/** If the msg is already queued, ignore this one and leave the old. */
@@ -713,6 +861,7 @@ public final class RemoteController
handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs);
}
+ ///////////// These calls are used by the old APIs with RCC and RCD //////////////////////
private void onNewPendingIntent(int genId, PendingIntent pi) {
synchronized(mGenLock) {
if (mClientGenerationIdCurrent != genId) {
@@ -848,6 +997,86 @@ public final class RemoteController
}
}
+ ///////////// These calls are used by the new APIs with Sessions //////////////////////
+ private void updateController(MediaController controller) {
+ if (DEBUG) {
+ Log.d(TAG, "Updating controller to " + controller + " previous controller is "
+ + mCurrentSession);
+ }
+ synchronized (mInfoLock) {
+ if (controller == null) {
+ if (mCurrentSession != null) {
+ mCurrentSession.removeCallback(mSessionCb);
+ mCurrentSession = null;
+ sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
+ 0 /* genId */, 1 /* clearing */, null /* obj */, 0 /* delay */);
+ }
+ } else if (mCurrentSession == null
+ || !controller.getSessionInfo().getId()
+ .equals(mCurrentSession.getSessionInfo().getId())) {
+ if (mCurrentSession != null) {
+ mCurrentSession.removeCallback(mSessionCb);
+ }
+ sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
+ 0 /* genId */, 0 /* clearing */, null /* obj */, 0 /* delay */);
+ mCurrentSession = controller;
+ mCurrentSession.addCallback(mSessionCb, mEventHandler);
+
+ PlaybackState state = controller.getPlaybackState();
+ sendMsg(mEventHandler, MSG_NEW_PLAYBACK_STATE, SENDMSG_REPLACE,
+ 0 /* genId */, 0, state /* obj */, 0 /* delay */);
+
+ MediaMetadata metadata = controller.getMetadata();
+ sendMsg(mEventHandler, MSG_NEW_MEDIA_METADATA, SENDMSG_REPLACE,
+ 0 /* arg1 */, 0 /* arg2 */, metadata /* obj */, 0 /* delay */);
+ }
+ // else same controller, no need to update
+ }
+ }
+
+ private void onNewPlaybackState(PlaybackState state) {
+ final OnClientUpdateListener l;
+ synchronized (mInfoLock) {
+ l = this.mOnClientUpdateListener;
+ }
+ if (l != null) {
+ int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE : PlaybackState
+ .getRccStateFromState(state.getState());
+ if (state == null || state.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
+ l.onClientPlaybackStateUpdate(playstate);
+ } else {
+ l.onClientPlaybackStateUpdate(playstate, state.getLastPositionUpdateTime(),
+ state.getPosition(), state.getPlaybackRate());
+ }
+ if (state != null) {
+ l.onClientTransportControlUpdate(PlaybackState.getRccControlFlagsFromActions(state
+ .getActions()));
+ }
+ }
+ }
+
+ private void onNewMediaMetadata(MediaMetadata metadata) {
+ if (metadata == null) {
+ // RemoteController only handles non-null metadata
+ return;
+ }
+ final OnClientUpdateListener l;
+ final MetadataEditor metadataEditor;
+ // prepare the received Bundle to be used inside a MetadataEditor
+ synchronized(mInfoLock) {
+ l = mOnClientUpdateListener;
+ boolean canRate = mCurrentSession != null
+ && mCurrentSession.getRatingType() != Rating.RATING_NONE;
+ long editableKeys = canRate ? MediaMetadataEditor.RATING_KEY_BY_USER : 0;
+ Bundle legacyMetadata = MediaSessionLegacyHelper.getOldMetadata(metadata);
+ mMetadataEditor = new MetadataEditor(legacyMetadata, editableKeys);
+ metadataEditor = mMetadataEditor;
+ }
+ if (l != null) {
+ l.onClientMetadataUpdate(metadataEditor);
+ }
+ }
+
//==================================================
private static class PlaybackInfo {
int mState;
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index 7c039079796a..f0cd7856ece7 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -37,6 +37,7 @@ interface ISessionController {
boolean isTransportControlEnabled();
void showRoutePicker();
MediaSessionInfo getSessionInfo();
+ long getFlags();
// These commands are for the TransportController
void play();
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 57a0a540c781..5ca7daa6560f 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -57,6 +57,7 @@ public final class MediaController {
private final Object mLock = new Object();
private boolean mCbRegistered = false;
+ private MediaSessionInfo mInfo;
private TransportControls mTransportController;
@@ -174,6 +175,21 @@ public final class MediaController {
}
/**
+ * Get the flags for this session.
+ *
+ * @return The current set of flags for the session.
+ * @hide
+ */
+ public long getFlags() {
+ try {
+ return mSessionBinder.getFlags();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling getFlags.", e);
+ }
+ return 0;
+ }
+
+ /**
* Adds a callback to receive updates from the Session. Updates will be
* posted on the caller's thread.
*
@@ -253,12 +269,14 @@ public final class MediaController {
* @hide
*/
public MediaSessionInfo getSessionInfo() {
- try {
- return mSessionBinder.getSessionInfo();
- } catch (RemoteException e) {
- Log.e(TAG, "Error in getSessionInfo.", e);
+ if (mInfo == null) {
+ try {
+ mInfo = mSessionBinder.getSessionInfo();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in getSessionInfo.", e);
+ }
}
- return null;
+ return mInfo;
}
/*
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 099f60152c65..801844fd9b01 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -20,6 +20,10 @@ import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Context;
import android.content.Intent;
+import android.media.MediaMetadata;
+import android.media.MediaMetadataEditor;
+import android.media.MediaMetadataRetriever;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.ArrayMap;
@@ -64,6 +68,88 @@ public class MediaSessionLegacyHelper {
return sInstance;
}
+ public static Bundle getOldMetadata(MediaMetadata metadata) {
+ Bundle oldMetadata = new Bundle();
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) {
+ oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ALBUM),
+ metadata.getString(MediaMetadata.METADATA_KEY_ALBUM));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_ART)) {
+ oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
+ metadata.getBitmap(MediaMetadata.METADATA_KEY_ART));
+ } else if (metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ART)) {
+ // Fall back to album art if the track art wasn't available
+ oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
+ metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ARTIST)) {
+ oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST),
+ metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
+ oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST),
+ metadata.getString(MediaMetadata.METADATA_KEY_ARTIST));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_AUTHOR)) {
+ oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_AUTHOR),
+ metadata.getString(MediaMetadata.METADATA_KEY_AUTHOR));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_COMPILATION)) {
+ oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_COMPILATION),
+ metadata.getString(MediaMetadata.METADATA_KEY_COMPILATION));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_COMPOSER)) {
+ oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_COMPOSER),
+ metadata.getString(MediaMetadata.METADATA_KEY_COMPOSER));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_DATE)) {
+ oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DATE),
+ metadata.getString(MediaMetadata.METADATA_KEY_DATE));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_DISC_NUMBER)) {
+ oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER),
+ metadata.getLong(MediaMetadata.METADATA_KEY_DISC_NUMBER));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+ oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION),
+ metadata.getLong(MediaMetadata.METADATA_KEY_DURATION));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
+ oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_GENRE),
+ metadata.getString(MediaMetadata.METADATA_KEY_GENRE));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
+ oldMetadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS),
+ metadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_RATING)) {
+ oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.RATING_KEY_BY_OTHERS),
+ metadata.getRating(MediaMetadata.METADATA_KEY_RATING));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_USER_RATING)) {
+ oldMetadata.putParcelable(String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER),
+ metadata.getRating(MediaMetadata.METADATA_KEY_USER_RATING));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
+ oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE),
+ metadata.getString(MediaMetadata.METADATA_KEY_TITLE));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
+ oldMetadata.putLong(
+ String.valueOf(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER),
+ metadata.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_WRITER)) {
+ oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_WRITER),
+ metadata.getString(MediaMetadata.METADATA_KEY_WRITER));
+ }
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_YEAR)) {
+ oldMetadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_YEAR),
+ metadata.getString(MediaMetadata.METADATA_KEY_YEAR));
+ }
+ return oldMetadata;
+ }
+
public MediaSession getSession(PendingIntent pi) {
SessionHolder holder = mSessions.get(pi);
return holder == null ? null : holder.mSession;
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 291bfc8f9de3..8eceee887bfa 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -96,10 +96,13 @@ public final class MediaSessionManager {
}
/**
- * Get a list of controllers for all ongoing sessions. This requires the
- * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
- * the calling app. You may also retrieve this list if your app is an
- * enabled notification listener using the
+ * Get a list of controllers for all ongoing sessions. The controllers will
+ * be provided in priority order with the most important controller at index
+ * 0.
+ * <p>
+ * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL
+ * permission be held by the calling app. You may also retrieve this list if
+ * your app is an enabled notification listener using the
* {@link NotificationListenerService} APIs, in which case you must pass the
* {@link ComponentName} of your enabled listener.
*
@@ -239,7 +242,8 @@ public final class MediaSessionManager {
/**
* Called when the list of active sessions has changed. This can be due
* to a session being added or removed or the order of sessions
- * changing.
+ * changing. The controllers will be provided in priority order with the
+ * most important controller at index 0.
*
* @param controllers The updated list of controllers for the user that
* changed.
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index e09ac3f136ce..3b3f24960a39 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -432,6 +432,8 @@ public final class PlaybackState implements Parcelable {
return STATE_REWINDING;
case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
return STATE_SKIPPING_TO_PREVIOUS;
+ case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
+ return STATE_SKIPPING_TO_NEXT;
case RemoteControlClient.PLAYSTATE_STOPPED:
return STATE_STOPPED;
default:
@@ -440,6 +442,41 @@ public final class PlaybackState implements Parcelable {
}
/**
+ * Get the {@link RemoteControlClient} state for the given
+ * {@link PlaybackState} state.
+ *
+ * @param state The state used by {@link PlaybackState}.
+ * @return The equivalent state used by {@link RemoteControlClient}.
+ * @hide
+ */
+ public static int getRccStateFromState(int state) {
+ switch (state) {
+ case STATE_BUFFERING:
+ return RemoteControlClient.PLAYSTATE_BUFFERING;
+ case STATE_ERROR:
+ return RemoteControlClient.PLAYSTATE_ERROR;
+ case STATE_FAST_FORWARDING:
+ return RemoteControlClient.PLAYSTATE_FAST_FORWARDING;
+ case STATE_NONE:
+ return RemoteControlClient.PLAYSTATE_NONE;
+ case STATE_PAUSED:
+ return RemoteControlClient.PLAYSTATE_PAUSED;
+ case STATE_PLAYING:
+ return RemoteControlClient.PLAYSTATE_PLAYING;
+ case STATE_REWINDING:
+ return RemoteControlClient.PLAYSTATE_REWINDING;
+ case STATE_SKIPPING_TO_PREVIOUS:
+ return RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS;
+ case STATE_SKIPPING_TO_NEXT:
+ return RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS;
+ case STATE_STOPPED:
+ return RemoteControlClient.PLAYSTATE_STOPPED;
+ default:
+ return -1;
+ }
+ }
+
+ /**
* @hide
*/
public static long getActionsFromRccControlFlags(int rccFlags) {
@@ -454,6 +491,21 @@ public final class PlaybackState implements Parcelable {
return actions;
}
+ /**
+ * @hide
+ */
+ public static int getRccControlFlagsFromActions(long actions) {
+ int rccFlags = 0;
+ long action = 1;
+ while (action <= actions && action < Integer.MAX_VALUE) {
+ if ((action & actions) != 0) {
+ rccFlags |= getRccFlagForAction(action);
+ }
+ action = action << 1;
+ }
+ return rccFlags;
+ }
+
private static long getActionForRccFlag(int flag) {
switch (flag) {
case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS:
@@ -480,6 +532,35 @@ public final class PlaybackState implements Parcelable {
return 0;
}
+ private static int getRccFlagForAction(long action) {
+ // We only care about the lower set of actions that can map to rcc
+ // flags.
+ int testAction = action < Integer.MAX_VALUE ? (int) action : 0;
+ switch (testAction) {
+ case (int) ACTION_SKIP_TO_PREVIOUS:
+ return RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS;
+ case (int) ACTION_REWIND:
+ return RemoteControlClient.FLAG_KEY_MEDIA_REWIND;
+ case (int) ACTION_PLAY:
+ return RemoteControlClient.FLAG_KEY_MEDIA_PLAY;
+ case (int) ACTION_PLAY_PAUSE:
+ return RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE;
+ case (int) ACTION_PAUSE:
+ return RemoteControlClient.FLAG_KEY_MEDIA_PAUSE;
+ case (int) ACTION_STOP:
+ return RemoteControlClient.FLAG_KEY_MEDIA_STOP;
+ case (int) ACTION_FAST_FORWARD:
+ return RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD;
+ case (int) ACTION_SKIP_TO_NEXT:
+ return RemoteControlClient.FLAG_KEY_MEDIA_NEXT;
+ case (int) ACTION_SEEK_TO:
+ return RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE;
+ case (int) ACTION_SET_RATING:
+ return RemoteControlClient.FLAG_KEY_MEDIA_RATING;
+ }
+ return 0;
+ }
+
public static final Parcelable.Creator<PlaybackState> CREATOR
= new Parcelable.Creator<PlaybackState>() {
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 835b0941abc2..9ae8aed08784 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -949,6 +949,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
@Override
+ public long getFlags() {
+ return mFlags;
+ }
+
+ @Override
public void play() throws RemoteException {
mSessionCb.play();
}