From c3c4babf8424f65b3d3d2700f60fae6e94e9cd00 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Fri, 19 Apr 2013 08:56:50 -0700 Subject: [PATCH] Opt-in mechanism for RemoteControlClient position anti-drift check RemoteControlClient has an interface for the framework to query the playback position. This mechanism is used to detect when the estimated position drifts from the real position by having the framework regularly poll (every 15s when playing at 1x) this interface and compare against the estimation. But this mechanism: - should only be used when IRemoteControlDisplay implementation care about position display - should not be used by default because the implementation of the position query interface might involve network traffic in some remote media player implementation for instance. This CL implements an opt-in mechanism to be used by implementators of IRemoteControlDisplay, to request the anti-drift mechanism to be turned on. bug 8120740 Change-Id: I1baa3e515546ac41e0ac9c3a41bfa3147ecf3d7f --- media/java/android/media/AudioManager.java | 26 ++++ media/java/android/media/AudioService.java | 53 ++++++++- media/java/android/media/IAudioService.aidl | 14 +++ .../android/media/IRemoteControlClient.aidl | 1 + .../android/media/RemoteControlClient.java | 111 ++++++++++++++---- 5 files changed, 182 insertions(+), 23 deletions(-) diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 56e98e4a92068..8295c5f5cdd14 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2281,6 +2281,32 @@ public class AudioManager { } } + /** + * @hide + * Controls whether a remote control display needs periodic checks of the RemoteControlClient + * playback position to verify that the estimated position has not drifted from the actual + * position. By default the check is not performed. + * The IRemoteControlDisplay must have been previously registered for this to have any effect. + * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled + * or disabled. No effect is null. + * @param wantsSync if true, RemoteControlClient instances which expose their playback position + * to the framework will regularly compare the estimated playback position with the actual + * position, and will update the IRemoteControlDisplay implementation whenever a drift is + * detected. + */ + public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, + boolean wantsSync) { + if (rcd == null) { + return; + } + IAudioService service = getService(); + try { + service.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in remoteControlDisplayWantsPlaybackPositionSync " + e); + } + } + /** * @hide * Request the user of a RemoteControlClient to seek to the given playback position. diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 0df4f822393bc..0cff77d9b2773 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -5085,7 +5085,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); pw.println(" IRCD: " + di.mRcDisplay + " -- w:" + di.mArtworkExpectedWidth + - " -- h:" + di.mArtworkExpectedHeight); + " -- h:" + di.mArtworkExpectedHeight+ + " -- wantsPosSync:" + di.mWantsPositionSync); } } } @@ -5689,6 +5690,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private IBinder mRcDisplayBinder; private int mArtworkExpectedWidth = -1; private int mArtworkExpectedHeight = -1; + private boolean mWantsPositionSync = false; public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) { if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h); @@ -5752,6 +5754,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { try { rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); + if (di.mWantsPositionSync) { + rcc.setWantsSyncForDisplay(di.mRcDisplay, true); + } } catch (RemoteException e) { Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e); } @@ -5905,6 +5910,52 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + /** + * Controls whether a remote control display needs periodic checks of the RemoteControlClient + * playback position to verify that the estimated position has not drifted from the actual + * position. By default the check is not performed. + * The IRemoteControlDisplay must have been previously registered for this to have any effect. + * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled + * or disabled. Not null. + * @param wantsSync if true, RemoteControlClient instances which expose their playback position + * to the framework will regularly compare the estimated playback position with the actual + * position, and will update the IRemoteControlDisplay implementation whenever a drift is + * detected. + */ + public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, + boolean wantsSync) { + synchronized(mRCStack) { + boolean rcdRegistered = false; + // store the information about this display + // (display stack traversal order doesn't matter). + final Iterator displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + di.mWantsPositionSync = wantsSync; + rcdRegistered = true; + break; + } + } + if (!rcdRegistered) { + return; + } + // notify all current RemoteControlClients + // (stack traversal order doesn't matter as we notify all RCCs) + final Iterator stackIterator = mRCStack.iterator(); + while (stackIterator.hasNext()) { + final RemoteControlStackEntry rcse = stackIterator.next(); + if (rcse.mRcClient != null) { + try { + rcse.mRcClient.setWantsSyncForDisplay(rcd, wantsSync); + } catch (RemoteException e) { + Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e); + } + } + } + } + } + public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) { // ignore position change requests if invalid generation ID synchronized(mRCStack) { diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 0d285fc171c20..fda8c1bd2eded 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -152,6 +152,20 @@ interface IAudioService { * display doesn't need to receive artwork. */ oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h); + /** + * Controls whether a remote control display needs periodic checks of the RemoteControlClient + * playback position to verify that the estimated position has not drifted from the actual + * position. By default the check is not performed. + * The IRemoteControlDisplay must have been previously registered for this to have any effect. + * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled + * or disabled. Not null. + * @param wantsSync if true, RemoteControlClient instances which expose their playback position + * to the framework will regularly compare the estimated playback position with the actual + * position, and will update the IRemoteControlDisplay implementation whenever a drift is + * detected. + */ + oneway void remoteControlDisplayWantsPlaybackPositionSync(in IRemoteControlDisplay rcd, + boolean wantsSync); /** * Request the user of a RemoteControlClient to seek to the given playback position. * @param generationId the RemoteControlClient generation counter for which this request is diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl index e4cee06927a93..223612940bbd5 100644 --- a/media/java/android/media/IRemoteControlClient.aidl +++ b/media/java/android/media/IRemoteControlClient.aidl @@ -47,5 +47,6 @@ oneway interface IRemoteControlClient void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h); void unplugRemoteControlDisplay(IRemoteControlDisplay rcd); void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h); + void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync); void seekTo(int clientGeneration, long timeMs); } \ No newline at end of file diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index f77ddc4297f66..c6ae9aade2c01 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -645,35 +645,42 @@ public class RemoteControlClient sendAudioServiceNewPlaybackState_syncCacheLock(); // handle automatic playback position refreshes - if (mEventHandler == null) { - return; - } - mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK); - if (timeInMs == PLAYBACK_POSITION_INVALID) { - // this playback state refresh has no known playback position, it's no use - // trying to see if there is any drift at this point - // (this also bypasses this mechanism for older apps that use the old - // setPlaybackState(int) API) - return; - } - if (playbackPositionShouldMove(mPlaybackState)) { - // playback position moving, schedule next position drift check - mEventHandler.sendMessageDelayed( - mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), - getCheckPeriodFromSpeed(playbackSpeed)); - } + initiateCheckForDrift_syncCacheLock(); } } } + private void initiateCheckForDrift_syncCacheLock() { + if (mEventHandler == null) { + return; + } + mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK); + if (!mNeedsPositionSync) { + return; + } + if (mPlaybackPositionMs < 0) { + // the current playback state has no known playback position, it's no use + // trying to see if there is any drift at this point + // (this also bypasses this mechanism for older apps that use the old + // setPlaybackState(int) API) + return; + } + if (playbackPositionShouldMove(mPlaybackState)) { + // playback position moving, schedule next position drift check + mEventHandler.sendMessageDelayed( + mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), + getCheckPeriodFromSpeed(mPlaybackSpeed)); + } + } + private void onPositionDriftCheck() { if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); } synchronized(mCacheLock) { - if ((mEventHandler == null) || (mPositionProvider == null)) { + if ((mEventHandler == null) || (mPositionProvider == null) || !mNeedsPositionSync) { return; } - if ((mPlaybackPositionMs == PLAYBACK_POSITION_INVALID) || (mPlaybackSpeed == 0.0f)) { - if (DEBUG) { Log.d(TAG, " no position or 0 speed, no check needed"); } + if ((mPlaybackPositionMs < 0) || (mPlaybackSpeed == 0.0f)) { + if (DEBUG) { Log.d(TAG, " no valid position or 0 speed, no check needed"); } return; } long estPos = mPlaybackPositionMs + (long) @@ -1011,6 +1018,12 @@ public class RemoteControlClient */ private final PendingIntent mRcMediaIntent; + /** + * Reflects whether any "plugged in" IRemoteControlDisplay has mWantsPositonSync set to true. + */ + // TODO consider using a ref count for IRemoteControlDisplay requiring sync instead + private boolean mNeedsPositionSync = false; + /** * A class to encapsulate all the information about a remote control display. * A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay @@ -1020,6 +1033,7 @@ public class RemoteControlClient private IRemoteControlDisplay mRcDisplay; private int mArtworkExpectedWidth; private int mArtworkExpectedHeight; + private boolean mWantsPositionSync = false; DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) { mRcDisplay = rcd; @@ -1109,6 +1123,14 @@ public class RemoteControlClient } } + public void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync) { + // only post messages, we can't block here + if ((mEventHandler != null) && (rcd != null)) { + mEventHandler.sendMessage(mEventHandler.obtainMessage( + MSG_DISPLAY_WANTS_POS_SYNC, wantsSync ? 1 : 0, 0/*arg2 ignored*/, rcd)); + } + } + public void seekTo(int generationId, long timeMs) { // only post messages, we can't block here if (mEventHandler != null) { @@ -1160,6 +1182,7 @@ public class RemoteControlClient private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9; private final static int MSG_SEEK_TO = 10; private final static int MSG_POSITION_DRIFT_CHECK = 11; + private final static int MSG_DISPLAY_WANTS_POS_SYNC = 12; private class EventHandler extends Handler { public EventHandler(RemoteControlClient rcc, Looper looper) { @@ -1210,6 +1233,9 @@ public class RemoteControlClient case MSG_POSITION_DRIFT_CHECK: onPositionDriftCheck(); break; + case MSG_DISPLAY_WANTS_POS_SYNC: + onDisplayWantsSync((IRemoteControlDisplay)msg.obj, msg.arg1 == 1); + break; default: Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); } @@ -1410,14 +1436,30 @@ public class RemoteControlClient /** pre-condition rcd != null */ private void onUnplugDisplay(IRemoteControlDisplay rcd) { synchronized(mCacheLock) { - final Iterator displayIterator = mRcDisplays.iterator(); + Iterator displayIterator = mRcDisplays.iterator(); while (displayIterator.hasNext()) { final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { displayIterator.remove(); - return; + break; } } + // list of RCDs has changed, reevaluate whether position check is still needed + boolean oldNeedsPositionSync = mNeedsPositionSync; + boolean newNeedsPositionSync = false; + displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (di.mWantsPositionSync) { + newNeedsPositionSync = true; + break; + } + } + mNeedsPositionSync = newNeedsPositionSync; + if (oldNeedsPositionSync != mNeedsPositionSync) { + // update needed? + initiateCheckForDrift_syncCacheLock(); + } } } @@ -1440,6 +1482,31 @@ public class RemoteControlClient } } + /** pre-condition rcd != null */ + private void onDisplayWantsSync(IRemoteControlDisplay rcd, boolean wantsSync) { + synchronized(mCacheLock) { + boolean oldNeedsPositionSync = mNeedsPositionSync; + boolean newNeedsPositionSync = false; + final Iterator displayIterator = mRcDisplays.iterator(); + // go through the list of RCDs and for each entry, check both whether this is the RCD + // that gets upated, and whether the list has one entry that wants position sync + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + di.mWantsPositionSync = wantsSync; + } + if (di.mWantsPositionSync) { + newNeedsPositionSync = true; + } + } + mNeedsPositionSync = newNeedsPositionSync; + if (oldNeedsPositionSync != mNeedsPositionSync) { + // update needed? + initiateCheckForDrift_syncCacheLock(); + } + } + } + private void onSeekTo(int generationId, long timeMs) { synchronized (mCacheLock) { if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) {