From 3261b537c5fdec824575a1f6ad6d8942715e82e2 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Mon, 1 Apr 2013 14:59:39 -0700 Subject: [PATCH] RemoteControlClient receives playback position change requests RemoteControlClient defines two listener interfaces for playback position, one to let the framework query the current playback position, the other to request playback to seek to a given position. Updated IRemoteControlDisplay interface to support passing info about whether the user of RemoteControlClient can provide a playback position, and receive a new one. Updated implementations of IRemoteControlDisplay to new interface. Bug 8120740 Change-Id: I1a5a969da4d0f8c9ad27f691919dd08f8653982b --- .../internal/widget/TransportControlView.java | 2 +- media/java/android/media/AudioManager.java | 20 +++ media/java/android/media/AudioService.java | 31 ++++- media/java/android/media/IAudioService.aidl | 27 +++- .../android/media/IRemoteControlClient.aidl | 1 + .../android/media/IRemoteControlDisplay.aidl | 11 +- .../android/media/RemoteControlClient.java | 118 ++++++++++++++---- .../KeyguardTransportControlView.java | 2 +- .../impl/keyguard/KeyguardUpdateMonitor.java | 2 +- 9 files changed, 177 insertions(+), 37 deletions(-) diff --git a/core/java/com/android/internal/widget/TransportControlView.java b/core/java/com/android/internal/widget/TransportControlView.java index 8ebe94cd35a21..ca797ebc3ee37 100644 --- a/core/java/com/android/internal/widget/TransportControlView.java +++ b/core/java/com/android/internal/widget/TransportControlView.java @@ -158,7 +158,7 @@ public class TransportControlView extends FrameLayout implements OnClickListener } } - public void setTransportControlFlags(int generationId, int flags) { + public void setTransportControlInfo(int generationId, int flags, int posCapabilities) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags) diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 6f284f8f0b0a1..18326184b109d 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2273,6 +2273,26 @@ public class AudioManager { } } + /** + * @hide + * Request the user of a RemoteControlClient to seek to the given playback position. + * @param generationId the RemoteControlClient generation counter for which this request is + * issued. Requests for an older generation than current one will be ignored. + * @param timeMs the time in ms to seek to, must be positive. + */ + public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + if (timeMs < 0) { + return; + } + IAudioService service = getService(); + try { + service.setRemoteControlClientPlaybackPosition(generationId, timeMs); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in setRccPlaybackPosition("+ generationId + ", " + + timeMs + ")", e); + } + } + /** * @hide * Reload audio settings. This method is called by Settings backup diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 9ded92250edb9..4b76f942da948 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -167,7 +167,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { 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; - + private static final int MSG_RCC_SEEK_REQUEST = 33; // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be // persisted @@ -4919,6 +4919,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } }; + /** + * Synchronization on mCurrentRcLock always inside a block synchronized on mRCStack + */ private final Object mCurrentRcLock = new Object(); /** * The one remote control client which will receive a request for display information. @@ -5190,6 +5193,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { " -- volMax: " + rcse.mPlaybackVolumeMax + " -- volObs: " + rcse.mRemoteVolumeObs); } + synchronized(mCurrentRcLock) { + pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen); + } } synchronized (mMainRemote) { pw.println("\nRemote Volume State:"); @@ -6018,6 +6024,29 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + sendMsg(mAudioHandler, MSG_RCC_SEEK_REQUEST, SENDMSG_QUEUE, generationId /* arg1 */, + 0 /* arg2 ignored*/, new Long(timeMs) /* obj */, 0 /* delay */); + } + + public void onSetRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + if(DEBUG_RC) Log.d(TAG, "onSetRemoteControlClientPlaybackPosition(genId=" + generationId + + ", timeMs=" + timeMs + ")"); + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if ((mCurrentRcClient != null) && (mCurrentRcClientGen == generationId)) { + // tell the current client to seek to the requested location + try { + mCurrentRcClient.seekTo(generationId, timeMs); + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead: "+e); + mCurrentRcClient = null; + } + } + } + } + } + public void setPlaybackInfoForRcc(int rccId, int what, int value) { sendMsg(mAudioHandler, MSG_RCC_NEW_PLAYBACK_INFO, SENDMSG_QUEUE, rccId /* arg1 */, what /* arg2 */, Integer.valueOf(value) /* obj */, 0 /* delay */); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index e21b26bf58024..9692b912a5367 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -126,11 +126,6 @@ interface IAudioService { oneway void registerMediaButtonEventReceiverForCalls(in ComponentName c); oneway void unregisterMediaButtonEventReceiverForCalls(); - int registerRemoteControlClient(in PendingIntent mediaIntent, - in IRemoteControlClient rcClient, in String callingPackageName); - oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent, - in IRemoteControlClient rcClient); - /** * Register an IRemoteControlDisplay. * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient @@ -157,9 +152,29 @@ interface IAudioService { * display doesn't need to receive artwork. */ oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h); + /** + * Request the user of a RemoteControlClient to seek to the given playback position. + * @param generationId the RemoteControlClient generation counter for which this request is + * issued. Requests for an older generation than current one will be ignored. + * @param timeMs the time in ms to seek to, must be positive. + */ + void setRemoteControlClientPlaybackPosition(int generationId, long timeMs); + + /** + * Do not use directly, use instead + * {@link android.media.AudioManager#registerRemoteControlClient(RemoteControlClient)} + */ + int registerRemoteControlClient(in PendingIntent mediaIntent, + in IRemoteControlClient rcClient, in String callingPackageName); + /** + * Do not use directly, use instead + * {@link android.media.AudioManager#unregisterRemoteControlClient(RemoteControlClient)} + */ + oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent, + in IRemoteControlClient rcClient); oneway void setPlaybackInfoForRcc(int rccId, int what, int value); - void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed); + void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed); int getRemoteStreamMaxVolume(); int getRemoteStreamVolume(); oneway void registerRemoteVolumeObserverForRcc(int rccId, in IRemoteVolumeObserver rvo); diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl index 5600263b197be..e4cee06927a93 100644 --- a/media/java/android/media/IRemoteControlClient.aidl +++ b/media/java/android/media/IRemoteControlClient.aidl @@ -47,4 +47,5 @@ oneway interface IRemoteControlClient void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h); void unplugRemoteControlDisplay(IRemoteControlDisplay rcd); void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h); + void seekTo(int clientGeneration, long timeMs); } \ No newline at end of file diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl index 095cf8018ef33..c70889c9237cd 100644 --- a/media/java/android/media/IRemoteControlDisplay.aidl +++ b/media/java/android/media/IRemoteControlDisplay.aidl @@ -43,7 +43,16 @@ oneway interface IRemoteControlDisplay void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs, float speed); - void setTransportControlFlags(int generationId, int transportControlFlags); + /** + * Sets the transport control flags and playback position capabilities of a client. + * @param generationId the current generation ID as known by this client + * @param transportControlFlags bitmask of the transport controls this client supports, see + * {@link RemoteControlClient#setTransportControlFlags(int)} + * @param posCapabilities a bit mask for playback position capabilities, see + * {@link RemoteControlClient#MEDIA_POSITION_READABLE} and + * {@link RemoteControlClient#MEDIA_POSITION_WRITABLE} + */ + void setTransportControlInfo(int generationId, int transportControlFlags, int posCapabilities); void setMetadata(int generationId, in Bundle metadata); diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index f186000a7e6c1..e076ef0044d79 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -278,11 +278,14 @@ public class RemoteControlClient public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7; /** * @hide - * (to be un-hidden and added in javadoc of setTransportControlFlags(int)) + * TODO un-hide and add in javadoc of setTransportControlFlags(int) * Flag indicating a RemoteControlClient can receive changes in the media playback position - * through the {@link #OnPlaybackPositionUpdateListener} interface. - * + * through the {@link #OnPlaybackPositionUpdateListener} interface. This flag must be set + * in order for components that display the RemoteControlClient information, to display and + * let the user control media playback position. * @see #setTransportControlFlags(int) + * @see #setPlaybackPositionProvider(PlaybackPositionProvider) + * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener) */ public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8; @@ -605,7 +608,7 @@ public class RemoteControlClient /** * @hide - * (to be un-hidden) + * TODO un-hide * 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: @@ -630,11 +633,6 @@ public class RemoteControlClient */ public void setPlaybackState(int state, long timeInMs, float playbackSpeed) { synchronized(mCacheLock) { - if (timeInMs != PLAYBACK_POSITION_INVALID) { - mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE; - } else { - mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE; - } if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs) || (mPlaybackSpeed != playbackSpeed)) { // store locally @@ -670,19 +668,20 @@ public class RemoteControlClient mTransportControlFlags = transportControlFlags; // send to remote control display if conditions are met - sendTransportControlFlags_syncCacheLock(); + sendTransportControlInfo_syncCacheLock(); } } /** * @hide - * (to be un-hidden) + * TODO un-hide * Interface definition for a callback to be invoked when the media playback position is * requested to be updated. + * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE */ public interface OnPlaybackPositionUpdateListener { /** - * Called on the listener to notify it that the playback head should be set at the given + * Called on the implementer 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 @@ -694,8 +693,25 @@ public class RemoteControlClient /** * @hide - * (to be un-hidden) - * Sets the listener RemoteControlClient calls whenever the media playback position is requested + * TODO un-hide + * Interface definition for a callback to be invoked when the media playback position is + * queried. + * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE + */ + public interface PlaybackPositionProvider { + /** + * Called on the implementer of the interface to query the current playback position. + * @return a negative value if the current playback position (or the last valid playback + * position) is not known, or a zero or positive value expressed in ms indicating the + * current position, or the last valid known position. + */ + long getPlaybackPosition(); + } + + /** + * @hide + * TODO un-hide + * Sets the listener to be called 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. @@ -703,16 +719,41 @@ public class RemoteControlClient */ public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) { synchronized(mCacheLock) { - if ((mPositionUpdateListener == null) && (l != null)) { + int oldCapa = mPlaybackPositionCapabilities; + if (l != null) { mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE; - // tell RCDs and AudioService this RCC accepts position updates - // TODO implement - } else if ((mPositionUpdateListener != null) && (l == null)) { + } else { mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE; - // tell RCDs and AudioService this RCC doesn't handle position updates - // TODO implement } mPositionUpdateListener = l; + if (oldCapa != mPlaybackPositionCapabilities) { + // tell RCDs that this RCC's playback position capabilities have changed + sendTransportControlInfo_syncCacheLock(); + } + } + } + + /** + * @hide + * TODO un-hide + * Sets the listener to be called whenever the media current playback position is needed. + * Queries will be received in the same thread as the one in which RemoteControlClient + * was created. + * @param l + */ + public void setPlaybackPositionProvider(PlaybackPositionProvider l) { + synchronized(mCacheLock) { + int oldCapa = mPlaybackPositionCapabilities; + if (l != null) { + mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE; + } else { + mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE; + } + mPositionProvider = l; + if (oldCapa != mPlaybackPositionCapabilities) { + // tell RCDs that this RCC's playback position capabilities have changed + sendTransportControlInfo_syncCacheLock(); + } } } @@ -895,6 +936,10 @@ public class RemoteControlClient * update requests. */ private OnPlaybackPositionUpdateListener mPositionUpdateListener; + /** + * Provider registered by user of RemoteControlClient to provide the current playback position. + */ + private PlaybackPositionProvider mPositionProvider; /** * The current remote control client generation ID across the system, as known by this object */ @@ -957,14 +1002,14 @@ public class RemoteControlClient */ private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() { - public void onInformationRequested(int clientGeneration, int infoFlags) { + public void onInformationRequested(int generationId, int infoFlags) { // only post messages, we can't block here if (mEventHandler != null) { // signal new client mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN); mEventHandler.dispatchMessage( mEventHandler.obtainMessage(MSG_NEW_INTERNAL_CLIENT_GEN, - /*arg1*/ clientGeneration, /*arg2, ignored*/ 0)); + /*arg1*/ generationId, /*arg2, ignored*/ 0)); // send the information mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE); mEventHandler.removeMessages(MSG_REQUEST_METADATA); @@ -1011,6 +1056,16 @@ public class RemoteControlClient MSG_UPDATE_DISPLAY_ARTWORK_SIZE, w, h, rcd)); } } + + public void seekTo(int generationId, long timeMs) { + // only post messages, we can't block here + if (mEventHandler != null) { + mEventHandler.removeMessages(MSG_SEEK_TO); + mEventHandler.dispatchMessage(mEventHandler.obtainMessage( + MSG_SEEK_TO, generationId /* arg1 */, 0 /* arg2, ignored */, + new Long(timeMs))); + } + } }; /** @@ -1051,6 +1106,7 @@ public class RemoteControlClient private final static int MSG_PLUG_DISPLAY = 7; private final static int MSG_UNPLUG_DISPLAY = 8; private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9; + private final static int MSG_SEEK_TO = 10; private class EventHandler extends Handler { public EventHandler(RemoteControlClient rcc, Looper looper) { @@ -1072,7 +1128,7 @@ public class RemoteControlClient break; case MSG_REQUEST_TRANSPORTCONTROL: synchronized (mCacheLock) { - sendTransportControlFlags_syncCacheLock(); + sendTransportControlInfo_syncCacheLock(); } break; case MSG_REQUEST_ARTWORK: @@ -1095,6 +1151,8 @@ public class RemoteControlClient case MSG_UPDATE_DISPLAY_ARTWORK_SIZE: onUpdateDisplayArtworkSize((IRemoteControlDisplay)msg.obj, msg.arg1, msg.arg2); break; + case MSG_SEEK_TO: + onSeekTo(msg.arg1, ((Long)msg.obj).longValue()); default: Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); } @@ -1136,14 +1194,14 @@ public class RemoteControlClient } } - private void sendTransportControlFlags_syncCacheLock() { + private void sendTransportControlInfo_syncCacheLock() { if (mCurrentClientGenId == mInternalClientGenId) { final Iterator displayIterator = mRcDisplays.iterator(); while (displayIterator.hasNext()) { final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); try { - di.mRcDisplay.setTransportControlFlags(mInternalClientGenId, - mTransportControlFlags); + di.mRcDisplay.setTransportControlInfo(mInternalClientGenId, + mTransportControlFlags, mPlaybackPositionCapabilities); } catch (RemoteException e) { Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay, e); @@ -1325,6 +1383,14 @@ public class RemoteControlClient } } + private void onSeekTo(int generationId, long timeMs) { + synchronized (mCacheLock) { + if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) { + mPositionUpdateListener.onPlaybackPositionUpdate(timeMs); + } + } + } + //=========================================================== // Internal utilities diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java index d5798d7be88b5..5e3b7da2f657c 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java @@ -147,7 +147,7 @@ public class KeyguardTransportControlView extends FrameLayout implements OnClick } } - public void setTransportControlFlags(int generationId, int flags) { + public void setTransportControlInfo(int generationId, int flags, int posCapabilities) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags) diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java index e958e9a2e7ede..159a92db7c48d 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java @@ -211,7 +211,7 @@ public class KeyguardUpdateMonitor { } - public void setTransportControlFlags(int generationId, int flags) { + public void setTransportControlInfo(int generationId, int flags, int posCapabilities) { }