Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c3c4babf authored by Jean-Michel Trivi's avatar Jean-Michel Trivi
Browse files

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
parent 9b3ebb12
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -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.
+52 −1
Original line number Diff line number Diff line
@@ -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<DisplayInfoForServer> 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<RemoteControlStackEntry> 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) {
+14 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+89 −22
Original line number Diff line number Diff line
@@ -645,12 +645,21 @@ public class RemoteControlClient
                sendAudioServiceNewPlaybackState_syncCacheLock();

                // handle automatic playback position refreshes
                initiateCheckForDrift_syncCacheLock();
            }
        }
    }

    private void initiateCheckForDrift_syncCacheLock() {
        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
        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)
@@ -660,20 +669,18 @@ public class RemoteControlClient
            // playback position moving, schedule next position drift check
            mEventHandler.sendMessageDelayed(
                    mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
                            getCheckPeriodFromSpeed(playbackSpeed));
                }
            }
                    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,13 +1436,29 @@ public class RemoteControlClient
    /** pre-condition rcd != null */
    private void onUnplugDisplay(IRemoteControlDisplay rcd) {
        synchronized(mCacheLock) {
            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
            Iterator<DisplayInfoForClient> 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<DisplayInfoForClient> 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)) {