Loading core/java/com/android/internal/widget/TransportControlView.java +2 −1 Original line number Diff line number Diff line Loading @@ -143,7 +143,8 @@ public class TransportControlView extends FrameLayout implements OnClickListener mLocalHandler = new WeakReference<Handler>(handler); } public void setPlaybackState(int generationId, int state, long stateChangeTimeMs) { public void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs, float speed) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_UPDATE_STATE, generationId, state).sendToTarget(); Loading media/java/android/media/AudioService.java +100 −19 Original line number Diff line number Diff line Loading @@ -166,6 +166,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private static final int MSG_PROMOTE_RCC = 29; private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 30; private static final int MSG_UNLOAD_SOUND_EFFECTS = 31; private static final int MSG_RCC_NEW_PLAYBACK_STATE = 32; // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be Loading Loading @@ -3741,6 +3742,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */, (IRemoteVolumeObserver)msg.obj /* rvo */); break; case MSG_RCC_NEW_PLAYBACK_STATE: onNewPlaybackStateForRcc(msg.arg1 /* rccId */, msg.arg2 /* state */, (RccPlaybackState)msg.obj /* newState */); break; case MSG_SET_RSX_CONNECTION_STATE: onSetRsxConnectionState(msg.arg1/*available*/, msg.arg2/*address*/); Loading Loading @@ -5001,6 +5006,59 @@ public class AudioService extends IAudioService.Stub implements OnFinished { */ private boolean mHasRemotePlayback; private static class RccPlaybackState { public int mState; public long mPositionMs; public float mSpeed; public RccPlaybackState(int state, long positionMs, float speed) { mState = state; mPositionMs = positionMs; mSpeed = speed; } public void reset() { mState = RemoteControlClient.PLAYSTATE_STOPPED; mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID; mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X; } @Override public String toString() { return stateToString() + ", " + ((mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) ? "PLAYBACK_POSITION_INVALID ," : String.valueOf(mPositionMs)) + "ms ," + mSpeed + "X"; } private String stateToString() { switch (mState) { case RemoteControlClient.PLAYSTATE_NONE: return "PLAYSTATE_NONE"; case RemoteControlClient.PLAYSTATE_STOPPED: return "PLAYSTATE_STOPPED"; case RemoteControlClient.PLAYSTATE_PAUSED: return "PLAYSTATE_PAUSED"; case RemoteControlClient.PLAYSTATE_PLAYING: return "PLAYSTATE_PLAYING"; case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: return "PLAYSTATE_FAST_FORWARDING"; case RemoteControlClient.PLAYSTATE_REWINDING: return "PLAYSTATE_REWINDING"; case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: return "PLAYSTATE_SKIPPING_FORWARDS"; case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: return "PLAYSTATE_SKIPPING_BACKWARDS"; case RemoteControlClient.PLAYSTATE_BUFFERING: return "PLAYSTATE_BUFFERING"; case RemoteControlClient.PLAYSTATE_ERROR: return "PLAYSTATE_ERROR"; default: return "[invalid playstate]"; } } } private static class RemoteControlStackEntry { public int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED; /** Loading Loading @@ -5029,7 +5087,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public int mPlaybackVolumeMax; public int mPlaybackVolumeHandling; public int mPlaybackStream; public int mPlaybackState; public RccPlaybackState mPlaybackState; public IRemoteVolumeObserver mRemoteVolumeObs; public void resetPlaybackInfo() { Loading @@ -5038,7 +5096,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; mPlaybackStream = AudioManager.STREAM_MUSIC; mPlaybackState = RemoteControlClient.PLAYSTATE_STOPPED; mPlaybackState.reset(); mRemoteVolumeObs = null; } Loading Loading @@ -6007,31 +6065,54 @@ public class AudioService extends IAudioService.Stub implements OnFinished { case RemoteControlClient.PLAYBACKINFO_USES_STREAM: rcse.mPlaybackStream = value; break; case RemoteControlClient.PLAYBACKINFO_PLAYSTATE: rcse.mPlaybackState = value; default: Log.e(TAG, "unhandled key " + key + " for RCC " + rccId); break; } return; } }//for } catch (ArrayIndexOutOfBoundsException e) { // not expected to happen, indicates improper concurrent modification Log.e(TAG, "Wrong index mRCStack on onNewPlaybackInfoForRcc, lock error? ", e); } } } public void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) { sendMsg(mAudioHandler, MSG_RCC_NEW_PLAYBACK_STATE, SENDMSG_QUEUE, rccId /* arg1 */, state /* arg2 */, new RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */); } public void onNewPlaybackStateForRcc(int rccId, int state, RccPlaybackState newState) { if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state + ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")"); synchronized(mRCStack) { // iterating from top of stack as playback information changes are more likely // on entries at the top of the remote control stack try { for (int index = mRCStack.size()-1; index >= 0; index--) { final RemoteControlStackEntry rcse = mRCStack.elementAt(index); if (rcse.mRccId == rccId) { rcse.mPlaybackState = newState; synchronized (mMainRemote) { if (rccId == mMainRemote.mRccId) { mMainRemoteIsActive = isPlaystateActive(value); mMainRemoteIsActive = isPlaystateActive(state); postReevaluateRemote(); } } // an RCC moving to a "playing" state should become the media button // event receiver so it can be controlled, without requiring the // app to re-register its receiver if (isPlaystateActive(value)) { if (isPlaystateActive(state)) { postPromoteRcc(rccId); } break; default: Log.e(TAG, "unhandled key " + key + " for RCC " + rccId); break; } return; } }//for } catch (ArrayIndexOutOfBoundsException e) { // not expected to happen, indicates improper concurrent modification Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); Log.e(TAG, "Wrong index on mRCStack in onNewPlaybackStateForRcc, lock error? ", e); } } } Loading Loading @@ -6075,7 +6156,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { for (int index = mRCStack.size()-1; index >= 0; index--) { final RemoteControlStackEntry rcse = mRCStack.elementAt(index); if ((rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) && isPlaystateActive(rcse.mPlaybackState) && isPlaystateActive(rcse.mPlaybackState.mState) && (rcse.mPlaybackStream == streamType)) { if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType + ", vol =" + rcse.mPlaybackVolume); Loading media/java/android/media/IAudioService.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -159,6 +159,7 @@ interface IAudioService { oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h); oneway void setPlaybackInfoForRcc(int rccId, int what, int value); void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed); int getRemoteStreamMaxVolume(); int getRemoteStreamVolume(); oneway void registerRemoteVolumeObserverForRcc(int rccId, in IRemoteVolumeObserver rvo); Loading media/java/android/media/IRemoteControlDisplay.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -40,7 +40,8 @@ oneway interface IRemoteControlDisplay void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent, boolean clearing); void setPlaybackState(int generationId, int state, long stateChangeTimeMs); void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs, float speed); void setTransportControlFlags(int generationId, int transportControlFlags); Loading media/java/android/media/RemoteControlClient.java +151 −16 Original line number Diff line number Diff line Loading @@ -172,6 +172,17 @@ public class RemoteControlClient */ public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE; /** * @hide * An unknown or invalid playback position value. */ public final static long PLAYBACK_POSITION_INVALID = -1; /** * @hide * The default playback speed, 1x. */ public final static float PLAYBACK_SPEED_1X = 1.0f; //========================================== // Public keys for playback information /** Loading Loading @@ -208,15 +219,7 @@ public class RemoteControlClient public final static int PLAYBACKINFO_USES_STREAM = 5; //========================================== // Private keys for playback information /** * @hide * Used internally to relay playback state (set by the application with * {@link #setPlaybackState(int)}) to AudioService */ public final static int PLAYBACKINFO_PLAYSTATE = 255; // Public flags for the supported transport control capabililities /** * Flag indicating a RemoteControlClient makes use of the "previous" media key. * Loading Loading @@ -273,6 +276,15 @@ public class RemoteControlClient * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT */ public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; /** * @hide * (to be un-hidden and added in javadoc of setTransportControlFlags(int)) * Flag indicating a RemoteControlClient can receive changes in the media playback position * through the {@link #OnPlaybackPositionUpdateListener} interface. * * @see #setTransportControlFlags(int) */ public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8; /** * @hide Loading Loading @@ -588,17 +600,54 @@ public class RemoteControlClient * {@link #PLAYSTATE_ERROR}. */ public void setPlaybackState(int state) { setPlaybackState(state, PLAYBACK_POSITION_INVALID, PLAYBACK_SPEED_1X); } /** * @hide * (to be un-hidden) * Sets the current playback state and the matching media position for the current playback * speed. * @param state The current playback state, one of the following values: * {@link #PLAYSTATE_STOPPED}, * {@link #PLAYSTATE_PAUSED}, * {@link #PLAYSTATE_PLAYING}, * {@link #PLAYSTATE_FAST_FORWARDING}, * {@link #PLAYSTATE_REWINDING}, * {@link #PLAYSTATE_SKIPPING_FORWARDS}, * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, * {@link #PLAYSTATE_BUFFERING}, * {@link #PLAYSTATE_ERROR}. * @param timeInMs a 0 or positive value for the current media position expressed in ms * (same unit as for when sending the media duration, if applicable, with * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the * {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not * known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state * is {@link #PLAYSTATE_BUFFERING} and nothing had played yet). * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback, * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is * playing (e.g. when state is {@link #PLAYSTATE_ERROR}). */ public void setPlaybackState(int state, long timeInMs, float playbackSpeed) { synchronized(mCacheLock) { if (mPlaybackState != state) { if (timeInMs != PLAYBACK_POSITION_INVALID) { mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE; } else { mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE; } if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs) || (mPlaybackSpeed != playbackSpeed)) { // store locally mPlaybackState = state; mPlaybackPositionMs = timeInMs; mPlaybackSpeed = playbackSpeed; // keep track of when the state change occurred mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime(); // send to remote control display if conditions are met sendPlaybackState_syncCacheLock(); // update AudioService sendAudioServiceNewPlaybackInfo_syncCacheLock(PLAYBACKINFO_PLAYSTATE, state); sendAudioServiceNewPlaybackState_syncCacheLock(); } } } Loading @@ -625,6 +674,65 @@ public class RemoteControlClient } } /** * @hide * (to be un-hidden) * Interface definition for a callback to be invoked when the media playback position is * requested to be updated. */ public interface OnPlaybackPositionUpdateListener { /** * Called on the listener to notify it that the playback head should be set at the given * position. If the position can be changed from its current value, the implementor of * the interface should also update the playback position using * {@link RemoteControlClient#setPlaybackState(int, long, int)} to reflect the actual new * position being used, regardless of whether it differs from the requested position. * @param newPositionMs the new requested position in the current media, expressed in ms. */ void onPlaybackPositionUpdate(long newPositionMs); } /** * @hide * (to be un-hidden) * Sets the listener RemoteControlClient calls whenever the media playback position is requested * to be updated. * Notifications will be received in the same thread as the one in which RemoteControlClient * was created. * @param l */ public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) { synchronized(mCacheLock) { if ((mPositionUpdateListener == null) && (l != null)) { mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE; // tell RCDs and AudioService this RCC accepts position updates // TODO implement } else if ((mPositionUpdateListener != null) && (l == null)) { mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE; // tell RCDs and AudioService this RCC doesn't handle position updates // TODO implement } mPositionUpdateListener = l; } } /** * @hide * Flag to reflect that the application controlling this RemoteControlClient sends playback * position updates. The playback position being "readable" is considered from the application's * point of view. */ public static int MEDIA_POSITION_READABLE = 1 << 0; /** * @hide * Flag to reflect that the application controlling this RemoteControlClient can receive * playback position updates. The playback position being "writable" * is considered from the application's point of view. */ public static int MEDIA_POSITION_WRITABLE = 1 << 1; private int mPlaybackPositionCapabilities = 0; /** @hide */ public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE; /** @hide */ Loading Loading @@ -755,6 +863,14 @@ public class RemoteControlClient * Access synchronized on mCacheLock */ private long mPlaybackStateChangeTimeMs = 0; /** * Last playback position in ms reported by the user */ private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID; /** * Last playback speed reported by the user */ private float mPlaybackSpeed = PLAYBACK_SPEED_1X; /** * Cache for the artwork bitmap. * Access synchronized on mCacheLock Loading @@ -774,9 +890,13 @@ public class RemoteControlClient * This is re-initialized in apply() and so cannot be final. */ private Bundle mMetadata = new Bundle(); /** * The current remote control client generation ID across the system * Listener registered by user of RemoteControlClient to receive requests for playback position * update requests. */ private OnPlaybackPositionUpdateListener mPositionUpdateListener; /** * The current remote control client generation ID across the system, as known by this object */ private int mCurrentClientGenId = -1; /** Loading @@ -789,7 +909,8 @@ public class RemoteControlClient /** * The media button intent description associated with this remote control client * (can / should include target component for intent handling) * (can / should include target component for intent handling, used when persisting media * button event receiver across reboots). */ private final PendingIntent mRcMediaIntent; Loading Loading @@ -990,7 +1111,8 @@ public class RemoteControlClient final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); try { di.mRcDisplay.setPlaybackState(mInternalClientGenId, mPlaybackState, mPlaybackStateChangeTimeMs); mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs, mPlaybackSpeed); } catch (RemoteException e) { Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e); displayIterator.remove(); Loading Loading @@ -1109,7 +1231,20 @@ public class RemoteControlClient try { service.setPlaybackInfoForRcc(mRcseId, what, value); } catch (RemoteException e) { Log.e(TAG, "Dead object in sendAudioServiceNewPlaybackInfo_syncCacheLock", e); Log.e(TAG, "Dead object in setPlaybackInfoForRcc", e); } } private void sendAudioServiceNewPlaybackState_syncCacheLock() { if (mRcseId == RCSE_ID_UNREGISTERED) { return; } IAudioService service = getService(); try { service.setPlaybackStateForRcc(mRcseId, mPlaybackState, mPlaybackPositionMs, mPlaybackSpeed); } catch (RemoteException e) { Log.e(TAG, "Dead object in setPlaybackStateForRcc", e); } } Loading Loading
core/java/com/android/internal/widget/TransportControlView.java +2 −1 Original line number Diff line number Diff line Loading @@ -143,7 +143,8 @@ public class TransportControlView extends FrameLayout implements OnClickListener mLocalHandler = new WeakReference<Handler>(handler); } public void setPlaybackState(int generationId, int state, long stateChangeTimeMs) { public void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs, float speed) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_UPDATE_STATE, generationId, state).sendToTarget(); Loading
media/java/android/media/AudioService.java +100 −19 Original line number Diff line number Diff line Loading @@ -166,6 +166,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private static final int MSG_PROMOTE_RCC = 29; private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 30; private static final int MSG_UNLOAD_SOUND_EFFECTS = 31; private static final int MSG_RCC_NEW_PLAYBACK_STATE = 32; // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be Loading Loading @@ -3741,6 +3742,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */, (IRemoteVolumeObserver)msg.obj /* rvo */); break; case MSG_RCC_NEW_PLAYBACK_STATE: onNewPlaybackStateForRcc(msg.arg1 /* rccId */, msg.arg2 /* state */, (RccPlaybackState)msg.obj /* newState */); break; case MSG_SET_RSX_CONNECTION_STATE: onSetRsxConnectionState(msg.arg1/*available*/, msg.arg2/*address*/); Loading Loading @@ -5001,6 +5006,59 @@ public class AudioService extends IAudioService.Stub implements OnFinished { */ private boolean mHasRemotePlayback; private static class RccPlaybackState { public int mState; public long mPositionMs; public float mSpeed; public RccPlaybackState(int state, long positionMs, float speed) { mState = state; mPositionMs = positionMs; mSpeed = speed; } public void reset() { mState = RemoteControlClient.PLAYSTATE_STOPPED; mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID; mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X; } @Override public String toString() { return stateToString() + ", " + ((mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) ? "PLAYBACK_POSITION_INVALID ," : String.valueOf(mPositionMs)) + "ms ," + mSpeed + "X"; } private String stateToString() { switch (mState) { case RemoteControlClient.PLAYSTATE_NONE: return "PLAYSTATE_NONE"; case RemoteControlClient.PLAYSTATE_STOPPED: return "PLAYSTATE_STOPPED"; case RemoteControlClient.PLAYSTATE_PAUSED: return "PLAYSTATE_PAUSED"; case RemoteControlClient.PLAYSTATE_PLAYING: return "PLAYSTATE_PLAYING"; case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: return "PLAYSTATE_FAST_FORWARDING"; case RemoteControlClient.PLAYSTATE_REWINDING: return "PLAYSTATE_REWINDING"; case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: return "PLAYSTATE_SKIPPING_FORWARDS"; case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: return "PLAYSTATE_SKIPPING_BACKWARDS"; case RemoteControlClient.PLAYSTATE_BUFFERING: return "PLAYSTATE_BUFFERING"; case RemoteControlClient.PLAYSTATE_ERROR: return "PLAYSTATE_ERROR"; default: return "[invalid playstate]"; } } } private static class RemoteControlStackEntry { public int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED; /** Loading Loading @@ -5029,7 +5087,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public int mPlaybackVolumeMax; public int mPlaybackVolumeHandling; public int mPlaybackStream; public int mPlaybackState; public RccPlaybackState mPlaybackState; public IRemoteVolumeObserver mRemoteVolumeObs; public void resetPlaybackInfo() { Loading @@ -5038,7 +5096,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; mPlaybackStream = AudioManager.STREAM_MUSIC; mPlaybackState = RemoteControlClient.PLAYSTATE_STOPPED; mPlaybackState.reset(); mRemoteVolumeObs = null; } Loading Loading @@ -6007,31 +6065,54 @@ public class AudioService extends IAudioService.Stub implements OnFinished { case RemoteControlClient.PLAYBACKINFO_USES_STREAM: rcse.mPlaybackStream = value; break; case RemoteControlClient.PLAYBACKINFO_PLAYSTATE: rcse.mPlaybackState = value; default: Log.e(TAG, "unhandled key " + key + " for RCC " + rccId); break; } return; } }//for } catch (ArrayIndexOutOfBoundsException e) { // not expected to happen, indicates improper concurrent modification Log.e(TAG, "Wrong index mRCStack on onNewPlaybackInfoForRcc, lock error? ", e); } } } public void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) { sendMsg(mAudioHandler, MSG_RCC_NEW_PLAYBACK_STATE, SENDMSG_QUEUE, rccId /* arg1 */, state /* arg2 */, new RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */); } public void onNewPlaybackStateForRcc(int rccId, int state, RccPlaybackState newState) { if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state + ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")"); synchronized(mRCStack) { // iterating from top of stack as playback information changes are more likely // on entries at the top of the remote control stack try { for (int index = mRCStack.size()-1; index >= 0; index--) { final RemoteControlStackEntry rcse = mRCStack.elementAt(index); if (rcse.mRccId == rccId) { rcse.mPlaybackState = newState; synchronized (mMainRemote) { if (rccId == mMainRemote.mRccId) { mMainRemoteIsActive = isPlaystateActive(value); mMainRemoteIsActive = isPlaystateActive(state); postReevaluateRemote(); } } // an RCC moving to a "playing" state should become the media button // event receiver so it can be controlled, without requiring the // app to re-register its receiver if (isPlaystateActive(value)) { if (isPlaystateActive(state)) { postPromoteRcc(rccId); } break; default: Log.e(TAG, "unhandled key " + key + " for RCC " + rccId); break; } return; } }//for } catch (ArrayIndexOutOfBoundsException e) { // not expected to happen, indicates improper concurrent modification Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); Log.e(TAG, "Wrong index on mRCStack in onNewPlaybackStateForRcc, lock error? ", e); } } } Loading Loading @@ -6075,7 +6156,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { for (int index = mRCStack.size()-1; index >= 0; index--) { final RemoteControlStackEntry rcse = mRCStack.elementAt(index); if ((rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) && isPlaystateActive(rcse.mPlaybackState) && isPlaystateActive(rcse.mPlaybackState.mState) && (rcse.mPlaybackStream == streamType)) { if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType + ", vol =" + rcse.mPlaybackVolume); Loading
media/java/android/media/IAudioService.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -159,6 +159,7 @@ interface IAudioService { oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h); oneway void setPlaybackInfoForRcc(int rccId, int what, int value); void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed); int getRemoteStreamMaxVolume(); int getRemoteStreamVolume(); oneway void registerRemoteVolumeObserverForRcc(int rccId, in IRemoteVolumeObserver rvo); Loading
media/java/android/media/IRemoteControlDisplay.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -40,7 +40,8 @@ oneway interface IRemoteControlDisplay void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent, boolean clearing); void setPlaybackState(int generationId, int state, long stateChangeTimeMs); void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs, float speed); void setTransportControlFlags(int generationId, int transportControlFlags); Loading
media/java/android/media/RemoteControlClient.java +151 −16 Original line number Diff line number Diff line Loading @@ -172,6 +172,17 @@ public class RemoteControlClient */ public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE; /** * @hide * An unknown or invalid playback position value. */ public final static long PLAYBACK_POSITION_INVALID = -1; /** * @hide * The default playback speed, 1x. */ public final static float PLAYBACK_SPEED_1X = 1.0f; //========================================== // Public keys for playback information /** Loading Loading @@ -208,15 +219,7 @@ public class RemoteControlClient public final static int PLAYBACKINFO_USES_STREAM = 5; //========================================== // Private keys for playback information /** * @hide * Used internally to relay playback state (set by the application with * {@link #setPlaybackState(int)}) to AudioService */ public final static int PLAYBACKINFO_PLAYSTATE = 255; // Public flags for the supported transport control capabililities /** * Flag indicating a RemoteControlClient makes use of the "previous" media key. * Loading Loading @@ -273,6 +276,15 @@ public class RemoteControlClient * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT */ public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; /** * @hide * (to be un-hidden and added in javadoc of setTransportControlFlags(int)) * Flag indicating a RemoteControlClient can receive changes in the media playback position * through the {@link #OnPlaybackPositionUpdateListener} interface. * * @see #setTransportControlFlags(int) */ public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8; /** * @hide Loading Loading @@ -588,17 +600,54 @@ public class RemoteControlClient * {@link #PLAYSTATE_ERROR}. */ public void setPlaybackState(int state) { setPlaybackState(state, PLAYBACK_POSITION_INVALID, PLAYBACK_SPEED_1X); } /** * @hide * (to be un-hidden) * Sets the current playback state and the matching media position for the current playback * speed. * @param state The current playback state, one of the following values: * {@link #PLAYSTATE_STOPPED}, * {@link #PLAYSTATE_PAUSED}, * {@link #PLAYSTATE_PLAYING}, * {@link #PLAYSTATE_FAST_FORWARDING}, * {@link #PLAYSTATE_REWINDING}, * {@link #PLAYSTATE_SKIPPING_FORWARDS}, * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, * {@link #PLAYSTATE_BUFFERING}, * {@link #PLAYSTATE_ERROR}. * @param timeInMs a 0 or positive value for the current media position expressed in ms * (same unit as for when sending the media duration, if applicable, with * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the * {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not * known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state * is {@link #PLAYSTATE_BUFFERING} and nothing had played yet). * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback, * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is * playing (e.g. when state is {@link #PLAYSTATE_ERROR}). */ public void setPlaybackState(int state, long timeInMs, float playbackSpeed) { synchronized(mCacheLock) { if (mPlaybackState != state) { if (timeInMs != PLAYBACK_POSITION_INVALID) { mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE; } else { mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE; } if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs) || (mPlaybackSpeed != playbackSpeed)) { // store locally mPlaybackState = state; mPlaybackPositionMs = timeInMs; mPlaybackSpeed = playbackSpeed; // keep track of when the state change occurred mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime(); // send to remote control display if conditions are met sendPlaybackState_syncCacheLock(); // update AudioService sendAudioServiceNewPlaybackInfo_syncCacheLock(PLAYBACKINFO_PLAYSTATE, state); sendAudioServiceNewPlaybackState_syncCacheLock(); } } } Loading @@ -625,6 +674,65 @@ public class RemoteControlClient } } /** * @hide * (to be un-hidden) * Interface definition for a callback to be invoked when the media playback position is * requested to be updated. */ public interface OnPlaybackPositionUpdateListener { /** * Called on the listener to notify it that the playback head should be set at the given * position. If the position can be changed from its current value, the implementor of * the interface should also update the playback position using * {@link RemoteControlClient#setPlaybackState(int, long, int)} to reflect the actual new * position being used, regardless of whether it differs from the requested position. * @param newPositionMs the new requested position in the current media, expressed in ms. */ void onPlaybackPositionUpdate(long newPositionMs); } /** * @hide * (to be un-hidden) * Sets the listener RemoteControlClient calls whenever the media playback position is requested * to be updated. * Notifications will be received in the same thread as the one in which RemoteControlClient * was created. * @param l */ public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) { synchronized(mCacheLock) { if ((mPositionUpdateListener == null) && (l != null)) { mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE; // tell RCDs and AudioService this RCC accepts position updates // TODO implement } else if ((mPositionUpdateListener != null) && (l == null)) { mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE; // tell RCDs and AudioService this RCC doesn't handle position updates // TODO implement } mPositionUpdateListener = l; } } /** * @hide * Flag to reflect that the application controlling this RemoteControlClient sends playback * position updates. The playback position being "readable" is considered from the application's * point of view. */ public static int MEDIA_POSITION_READABLE = 1 << 0; /** * @hide * Flag to reflect that the application controlling this RemoteControlClient can receive * playback position updates. The playback position being "writable" * is considered from the application's point of view. */ public static int MEDIA_POSITION_WRITABLE = 1 << 1; private int mPlaybackPositionCapabilities = 0; /** @hide */ public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE; /** @hide */ Loading Loading @@ -755,6 +863,14 @@ public class RemoteControlClient * Access synchronized on mCacheLock */ private long mPlaybackStateChangeTimeMs = 0; /** * Last playback position in ms reported by the user */ private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID; /** * Last playback speed reported by the user */ private float mPlaybackSpeed = PLAYBACK_SPEED_1X; /** * Cache for the artwork bitmap. * Access synchronized on mCacheLock Loading @@ -774,9 +890,13 @@ public class RemoteControlClient * This is re-initialized in apply() and so cannot be final. */ private Bundle mMetadata = new Bundle(); /** * The current remote control client generation ID across the system * Listener registered by user of RemoteControlClient to receive requests for playback position * update requests. */ private OnPlaybackPositionUpdateListener mPositionUpdateListener; /** * The current remote control client generation ID across the system, as known by this object */ private int mCurrentClientGenId = -1; /** Loading @@ -789,7 +909,8 @@ public class RemoteControlClient /** * The media button intent description associated with this remote control client * (can / should include target component for intent handling) * (can / should include target component for intent handling, used when persisting media * button event receiver across reboots). */ private final PendingIntent mRcMediaIntent; Loading Loading @@ -990,7 +1111,8 @@ public class RemoteControlClient final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); try { di.mRcDisplay.setPlaybackState(mInternalClientGenId, mPlaybackState, mPlaybackStateChangeTimeMs); mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs, mPlaybackSpeed); } catch (RemoteException e) { Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e); displayIterator.remove(); Loading Loading @@ -1109,7 +1231,20 @@ public class RemoteControlClient try { service.setPlaybackInfoForRcc(mRcseId, what, value); } catch (RemoteException e) { Log.e(TAG, "Dead object in sendAudioServiceNewPlaybackInfo_syncCacheLock", e); Log.e(TAG, "Dead object in setPlaybackInfoForRcc", e); } } private void sendAudioServiceNewPlaybackState_syncCacheLock() { if (mRcseId == RCSE_ID_UNREGISTERED) { return; } IAudioService service = getService(); try { service.setPlaybackStateForRcc(mRcseId, mPlaybackState, mPlaybackPositionMs, mPlaybackSpeed); } catch (RemoteException e) { Log.e(TAG, "Dead object in setPlaybackStateForRcc", e); } } Loading