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