diff options
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(); } |