Merge "Opt-in mechanism for RemoteControlClient position anti-drift check" into jb-mr2-dev
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 56e98e4..8295c5f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2283,6 +2283,32 @@
 
     /**
      * @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.
      * @param generationId the RemoteControlClient generation counter for which this request is
      *         issued. Requests for an older generation than current one will be ignored.
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 0df4f82..0cff77d9 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -5085,7 +5085,8 @@
                 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 @@
         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 @@
             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 @@
         }
     }
 
+    /**
+     * 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) {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 0d285fc..fda8c1b 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -153,6 +153,20 @@
      */
     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
      *         issued. Requests for an older generation than current one will be ignored.
diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl
index e4cee06..2236129 100644
--- a/media/java/android/media/IRemoteControlClient.aidl
+++ b/media/java/android/media/IRemoteControlClient.aidl
@@ -47,5 +47,6 @@
     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 f77ddc4..c6ae9aa 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -645,35 +645,42 @@
                 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)
@@ -1012,6 +1019,12 @@
     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 @@
         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 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 @@
     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 @@
                 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 @@
     /** 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 @@
         }
     }
 
+    /** 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)) {