Merge "Filter media controls by user" into rvc-dev
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index cd27fdf..749b537 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -290,6 +290,12 @@
     /** Whether we're in the middle of dragging the stack around by touch. */
     private boolean mIsDraggingStack = false;
 
+    /**
+     * The pointer index of the ACTION_DOWN event we received prior to an ACTION_UP. We'll ignore
+     * touches from other pointer indices.
+     */
+    private int mPointerIndexDown = -1;
+
     /** Description of current animation controller state. */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Stack view state:");
@@ -2220,6 +2226,18 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (ev.getAction() != MotionEvent.ACTION_DOWN && ev.getActionIndex() != mPointerIndexDown) {
+            // Ignore touches from additional pointer indices.
+            return false;
+        }
+
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mPointerIndexDown = ev.getActionIndex();
+        } else if (ev.getAction() == MotionEvent.ACTION_UP
+                || ev.getAction() == MotionEvent.ACTION_CANCEL) {
+            mPointerIndexDown = -1;
+        }
+
         boolean dispatched = super.dispatchTouchEvent(ev);
 
         // If a new bubble arrives while the collapsed stack is being dragged, it will be positioned
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 043b368..127c5dd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -248,6 +248,7 @@
             val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT)
             existingPlayer.view?.player?.setLayoutParams(lp)
+            existingPlayer.bind(data)
             existingPlayer.setListening(currentlyExpanded)
             updatePlayerToState(existingPlayer, noAnimation = true)
             if (existingPlayer.isPlaying) {
@@ -255,16 +256,18 @@
             } else {
                 mediaContent.addView(existingPlayer.view?.player)
             }
-        } else if (existingPlayer.isPlaying &&
-                mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
-            if (visualStabilityManager.isReorderingAllowed) {
-                mediaContent.removeView(existingPlayer.view?.player)
-                mediaContent.addView(existingPlayer.view?.player, 0)
-            } else {
-                needsReordering = true
+        } else {
+            existingPlayer.bind(data)
+            if (existingPlayer.isPlaying &&
+                    mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
+                if (visualStabilityManager.isReorderingAllowed) {
+                    mediaContent.removeView(existingPlayer.view?.player)
+                    mediaContent.addView(existingPlayer.view?.player, 0)
+                } else {
+                    needsReordering = true
+                }
             }
         }
-        existingPlayer?.bind(data)
         updatePageIndicator()
         mediaCarouselScrollHandler.onPlayersChanged()
         mediaCarousel.requiresRemeasuring = true
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 9a134db..8662aac 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -54,7 +54,32 @@
         if (mediaListeners.containsKey(key)) {
             return
         }
+        // Having an old key means that we're migrating from/to resumption. We should invalidate
+        // the old listener and create a new one.
+        val migrating = oldKey != null && key != oldKey
+        var wasPlaying = false
+        if (migrating) {
+            if (mediaListeners.containsKey(oldKey)) {
+                val oldListener = mediaListeners.remove(oldKey)
+                wasPlaying = oldListener?.playing ?: false
+                oldListener?.destroy()
+                if (DEBUG) Log.d(TAG, "migrating key $oldKey to $key, for resumption")
+            } else {
+                Log.w(TAG, "Old key $oldKey for player $key doesn't exist. Continuing...")
+            }
+        }
         mediaListeners[key] = PlaybackStateListener(key, data)
+
+        // If a player becomes active because of a migration, we'll need to broadcast its state.
+        // Doing it now would lead to reentrant callbacks, so let's wait until we're done.
+        if (migrating && mediaListeners[key]?.playing != wasPlaying) {
+            mainExecutor.execute {
+                if (mediaListeners[key]?.playing == true) {
+                    if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key")
+                    timeoutCallback.invoke(key, false /* timedOut */)
+                }
+            }
+        }
     }
 
     override fun onMediaDataRemoved(key: String) {
@@ -71,7 +96,7 @@
     ) : MediaController.Callback() {
 
         var timedOut = false
-        private var playing: Boolean? = null
+        var playing: Boolean? = null
 
         // Resume controls may have null token
         private val mediaController = if (data.token != null) {
@@ -83,7 +108,9 @@
 
         init {
             mediaController?.registerCallback(this)
-            onPlaybackStateChanged(mediaController?.playbackState)
+            // Let's register the cancellations, but not dispatch events now.
+            // Timeouts didn't happen yet and reentrant events are troublesome.
+            processState(mediaController?.playbackState, dispatchEvents = false)
         }
 
         fun destroy() {
@@ -91,8 +118,12 @@
         }
 
         override fun onPlaybackStateChanged(state: PlaybackState?) {
+            processState(state, dispatchEvents = true)
+        }
+
+        private fun processState(state: PlaybackState?, dispatchEvents: Boolean) {
             if (DEBUG) {
-                Log.v(TAG, "onPlaybackStateChanged: $state")
+                Log.v(TAG, "processState: $state")
             }
 
             val isPlaying = state != null && isPlayingState(state.state)
@@ -116,12 +147,16 @@
                         Log.v(TAG, "Execute timeout for $key")
                     }
                     timedOut = true
-                    timeoutCallback(key, timedOut)
+                    if (dispatchEvents) {
+                        timeoutCallback(key, timedOut)
+                    }
                 }, PAUSED_MEDIA_TIMEOUT)
             } else {
                 expireMediaTimeout(key, "playback started - $state, $key")
                 timedOut = false
-                timeoutCallback(key, timedOut)
+                if (dispatchEvents) {
+                    timeoutCallback(key, timedOut)
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 83cfdd5..38817d7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -268,7 +268,6 @@
     fun attach(transitionLayout: TransitionLayout) {
         this.transitionLayout = transitionLayout
         layoutController.attach(transitionLayout)
-        ensureAllMeasurements()
         if (currentEndLocation == -1) {
             return
         }
@@ -414,13 +413,16 @@
      * Clear all existing measurements and refresh the state to match the view.
      */
     fun refreshState() {
-        if (!firstRefresh) {
-            // Let's clear all of our measurements and recreate them!
-            viewStates.clear()
-            setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress,
-                    applyImmediately = true)
+        // Let's clear all of our measurements and recreate them!
+        viewStates.clear()
+        if (firstRefresh) {
+            // This is the first bind, let's ensure we pre-cache all measurements. Otherwise
+            // We'll just load these on demand.
+            ensureAllMeasurements()
+            firstRefresh = false
         }
-        firstRefresh = false
+        setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress,
+                applyImmediately = true)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 75d3d04..7d35416 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -36,7 +36,6 @@
 import android.util.Pair;
 import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
-import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
 import com.android.systemui.Dependency;
@@ -176,7 +175,7 @@
         @Override
         public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
                 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
-            if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode()
+            if (task.configuration.windowConfiguration.getWindowingMode()
                     != WINDOWING_MODE_PINNED) {
                 return;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 6282236..4b2c273 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -716,7 +716,7 @@
         @Override
         public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
                 boolean clearedTask, boolean wasVisible) {
-            if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode()
+            if (task.configuration.windowConfiguration.getWindowingMode()
                     != WINDOWING_MODE_PINNED) {
                 return;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 5628a24..739d30c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -100,12 +100,7 @@
         PAUSED_MEDIA_STATES.add(PlaybackState.STATE_STOPPED);
         PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED);
         PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
-    }
-    private static final HashSet<Integer> INACTIVE_MEDIA_STATES = new HashSet<>();
-    static {
-        INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_NONE);
-        INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_STOPPED);
-        INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
+        PAUSED_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING);
     }
 
     private final NotificationEntryManager mEntryManager;
@@ -262,15 +257,6 @@
         return !PAUSED_MEDIA_STATES.contains(state);
     }
 
-    /**
-     * Check if a state should be considered active (playing or paused)
-     * @param state a PlaybackState
-     * @return true if active
-     */
-    public static boolean isActiveState(int state) {
-        return !INACTIVE_MEDIA_STATES.contains(state);
-    }
-
     public void setUpWithPresenter(NotificationPresenter presenter) {
         mPresenter = presenter;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index bc14362..7a8e4f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -32,7 +32,9 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
@@ -119,6 +121,7 @@
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
         verify(executor).executeDelayed(capture(timeoutCaptor), anyLong())
+        verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
     }
 
     @Test
@@ -134,6 +137,24 @@
     }
 
     @Test
+    fun testOnMediaDataLoaded_migratesKeys() {
+        // From not playing
+        mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+        clearInvocations(mediaController)
+
+        // To playing
+        val playingState = mock(android.media.session.PlaybackState::class.java)
+        `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
+        `when`(mediaController.playbackState).thenReturn(playingState)
+        mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData)
+        verify(mediaController).unregisterCallback(anyObject())
+        verify(mediaController).registerCallback(anyObject())
+
+        // Enqueues callback
+        verify(executor).execute(anyObject())
+    }
+
+    @Test
     fun testOnPlaybackStateChanged_schedulesTimeout_whenPaused() {
         // Assuming we're registered
         testOnMediaDataLoaded_registersPlaybackListener()
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 407b9fc..edb3a9c 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -2177,7 +2177,7 @@
                 // split-screen in split-screen.
                 mService.getTaskChangeNotificationController()
                         .notifyActivityDismissingDockedStack();
-                taskDisplayArea.onSplitScreenModeDismissed(task.getStack());
+                taskDisplayArea.onSplitScreenModeDismissed((ActivityStack) task);
                 taskDisplayArea.mDisplayContent.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
                         true /* notifyClients */);
             }