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) { }