Loading media/java/android/media/AudioManager.java +28 −19 Original line number Diff line number Diff line Loading @@ -2344,6 +2344,10 @@ public class AudioManager { if (rctlr == null) { 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()); Loading @@ -2359,6 +2363,7 @@ public class AudioManager { return false; } } } /** * Unregisters a {@link RemoteController}, causing it to no longer receive media metadata and Loading @@ -2369,6 +2374,9 @@ public class AudioManager { if (rctlr == null) { return; } if (USE_SESSIONS) { rctlr.stopListeningToSessions(); } else { IAudioService service = getService(); try { service.unregisterRemoteControlDisplay(rctlr.getRcDisplay()); Loading @@ -2377,6 +2385,7 @@ public class AudioManager { Log.e(TAG, "Dead object in unregisterRemoteControlDisplay " + e); } } } /** * @hide Loading media/java/android/media/RemoteController.java +286 −57 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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; Loading @@ -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 */ Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -194,9 +211,16 @@ public final class RemoteController * @hide */ public String getRemoteControlClientPackageName() { if (USE_SESSIONS) { synchronized (mInfoLock) { return mCurrentSession != null ? mCurrentSession.getSessionInfo().getPackageName() : null; } } else { return mClientPendingIntentCurrent != null ? mClientPendingIntentCurrent.getCreatorPackage() : null; } } /** * Return the estimated playback position of the current media track or a negative value Loading @@ -215,23 +239,39 @@ 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(); } } } } 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 = mLastPlaybackInfo.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 then = lastPlaybackInfo.mStateChangeTimeMs; final long sinceThen = now - then; final long scaledSinceThen = (long) (sinceThen * mLastPlaybackInfo.mSpeed); final long scaledSinceThen = (long) (sinceThen * lastPlaybackInfo.mSpeed); return thenPos + scaledSinceThen; } } return -1; } Loading Loading @@ -267,10 +307,19 @@ public final class RemoteController if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) { throw new IllegalArgumentException("not a media key event"); } if (USE_SESSIONS) { synchronized (mInfoLock) { if (mCurrentSession != null) { return mCurrentSession.dispatchMediaButtonEvent(keyEvent); } return false; } } else { final PendingIntent pi; synchronized (mInfoLock) { if (!mIsRegistered) { Log.e(TAG, "Cannot use sendMediaKeyEvent() from an unregistered RemoteController"); Log.e(TAG, "Cannot use sendMediaKeyEvent() from an unregistered RemoteController"); return false; } if (!mEnabled) { Loading @@ -292,6 +341,7 @@ public final class RemoteController Log.i(TAG, "No-op when sending key click, no receiver right now"); return false; } } return true; } Loading @@ -311,11 +361,19 @@ public final class RemoteController if (timeMs < 0) { throw new IllegalArgumentException("illegal negative time value"); } if (USE_SESSIONS) { synchronized (mInfoLock) { if (mCurrentSession != null) { mCurrentSession.getTransportControls().seekTo(timeMs); } } } else { final int genId; synchronized (mGenLock) { genId = mClientGenerationIdCurrent; } mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs); } return true; } Loading Loading @@ -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. Loading Loading @@ -477,6 +534,20 @@ public final class RemoteController if (!mMetadataChanged) { return; } 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; Loading @@ -492,12 +563,13 @@ public final class RemoteController } 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; } } } Loading Loading @@ -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; Loading @@ -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 { Loading Loading @@ -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. */ Loading @@ -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) { Loading Loading @@ -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; Loading media/java/android/media/session/ISessionController.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ interface ISessionController { boolean isTransportControlEnabled(); void showRoutePicker(); MediaSessionInfo getSessionInfo(); long getFlags(); // These commands are for the TransportController void play(); Loading media/java/android/media/session/MediaController.java +23 −5 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ public final class MediaController { private final Object mLock = new Object(); private boolean mCbRegistered = false; private MediaSessionInfo mInfo; private TransportControls mTransportController; Loading Loading @@ -173,6 +174,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. Loading Loading @@ -253,12 +269,14 @@ public final class MediaController { * @hide */ public MediaSessionInfo getSessionInfo() { if (mInfo == null) { try { return mSessionBinder.getSessionInfo(); mInfo = mSessionBinder.getSessionInfo(); } catch (RemoteException e) { Log.e(TAG, "Error in getSessionInfo.", e); } return null; } return mInfo; } /* Loading media/java/android/media/session/MediaSessionLegacyHelper.java +86 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading
media/java/android/media/AudioManager.java +28 −19 Original line number Diff line number Diff line Loading @@ -2344,6 +2344,10 @@ public class AudioManager { if (rctlr == null) { 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()); Loading @@ -2359,6 +2363,7 @@ public class AudioManager { return false; } } } /** * Unregisters a {@link RemoteController}, causing it to no longer receive media metadata and Loading @@ -2369,6 +2374,9 @@ public class AudioManager { if (rctlr == null) { return; } if (USE_SESSIONS) { rctlr.stopListeningToSessions(); } else { IAudioService service = getService(); try { service.unregisterRemoteControlDisplay(rctlr.getRcDisplay()); Loading @@ -2377,6 +2385,7 @@ public class AudioManager { Log.e(TAG, "Dead object in unregisterRemoteControlDisplay " + e); } } } /** * @hide Loading
media/java/android/media/RemoteController.java +286 −57 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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; Loading @@ -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 */ Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -194,9 +211,16 @@ public final class RemoteController * @hide */ public String getRemoteControlClientPackageName() { if (USE_SESSIONS) { synchronized (mInfoLock) { return mCurrentSession != null ? mCurrentSession.getSessionInfo().getPackageName() : null; } } else { return mClientPendingIntentCurrent != null ? mClientPendingIntentCurrent.getCreatorPackage() : null; } } /** * Return the estimated playback position of the current media track or a negative value Loading @@ -215,23 +239,39 @@ 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(); } } } } 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 = mLastPlaybackInfo.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 then = lastPlaybackInfo.mStateChangeTimeMs; final long sinceThen = now - then; final long scaledSinceThen = (long) (sinceThen * mLastPlaybackInfo.mSpeed); final long scaledSinceThen = (long) (sinceThen * lastPlaybackInfo.mSpeed); return thenPos + scaledSinceThen; } } return -1; } Loading Loading @@ -267,10 +307,19 @@ public final class RemoteController if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) { throw new IllegalArgumentException("not a media key event"); } if (USE_SESSIONS) { synchronized (mInfoLock) { if (mCurrentSession != null) { return mCurrentSession.dispatchMediaButtonEvent(keyEvent); } return false; } } else { final PendingIntent pi; synchronized (mInfoLock) { if (!mIsRegistered) { Log.e(TAG, "Cannot use sendMediaKeyEvent() from an unregistered RemoteController"); Log.e(TAG, "Cannot use sendMediaKeyEvent() from an unregistered RemoteController"); return false; } if (!mEnabled) { Loading @@ -292,6 +341,7 @@ public final class RemoteController Log.i(TAG, "No-op when sending key click, no receiver right now"); return false; } } return true; } Loading @@ -311,11 +361,19 @@ public final class RemoteController if (timeMs < 0) { throw new IllegalArgumentException("illegal negative time value"); } if (USE_SESSIONS) { synchronized (mInfoLock) { if (mCurrentSession != null) { mCurrentSession.getTransportControls().seekTo(timeMs); } } } else { final int genId; synchronized (mGenLock) { genId = mClientGenerationIdCurrent; } mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs); } return true; } Loading Loading @@ -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. Loading Loading @@ -477,6 +534,20 @@ public final class RemoteController if (!mMetadataChanged) { return; } 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; Loading @@ -492,12 +563,13 @@ public final class RemoteController } 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; } } } Loading Loading @@ -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; Loading @@ -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 { Loading Loading @@ -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. */ Loading @@ -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) { Loading Loading @@ -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; Loading
media/java/android/media/session/ISessionController.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ interface ISessionController { boolean isTransportControlEnabled(); void showRoutePicker(); MediaSessionInfo getSessionInfo(); long getFlags(); // These commands are for the TransportController void play(); Loading
media/java/android/media/session/MediaController.java +23 −5 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ public final class MediaController { private final Object mLock = new Object(); private boolean mCbRegistered = false; private MediaSessionInfo mInfo; private TransportControls mTransportController; Loading Loading @@ -173,6 +174,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. Loading Loading @@ -253,12 +269,14 @@ public final class MediaController { * @hide */ public MediaSessionInfo getSessionInfo() { if (mInfo == null) { try { return mSessionBinder.getSessionInfo(); mInfo = mSessionBinder.getSessionInfo(); } catch (RemoteException e) { Log.e(TAG, "Error in getSessionInfo.", e); } return null; } return mInfo; } /* Loading
media/java/android/media/session/MediaSessionLegacyHelper.java +86 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading