Merge "Make fetch thumbnails call on bg thread to reduce thread switching" into main
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
index fd4cf0e..0abd88c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
@@ -299,7 +299,7 @@
         private final PointF mTouchDownLocation = new PointF();
         private final PointF mViewInitialPosition = new PointF();
         private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
-        private final long mPressToDragTimeout = ViewConfiguration.getLongPressTimeout() / 2;
+        private final long mPressToDragTimeout = ViewConfiguration.getLongPressTimeout();
         private State mState = State.IDLE;
         private int mTouchSlop = -1;
         private BubbleDragAnimator mAnimator;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
index 6b61298..f3f73c0 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
@@ -53,7 +53,7 @@
     private final int mTouchSlop;
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
-    private final long mTimeForTap;
+    private final long mTimeForLongPress;
     private int mActivePointerId = INVALID_POINTER_ID;
 
     public BubbleBarInputConsumer(Context context, BubbleControllers bubbleControllers,
@@ -64,7 +64,7 @@
 
         mInputMonitorCompat = inputMonitorCompat;
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
-        mTimeForTap = ViewConfiguration.getTapTimeout();
+        mTimeForLongPress = ViewConfiguration.getLongPressTimeout();
     }
 
     @Override
@@ -110,7 +110,8 @@
             case MotionEvent.ACTION_UP:
                 boolean swipeUpOnBubbleHandle = mBubbleBarSwipeController != null
                         && mBubbleBarSwipeController.isSwipeGesture();
-                boolean isWithinTapTime = ev.getEventTime() - ev.getDownTime() <= mTimeForTap;
+                // Anything less than a long-press is a tap
+                boolean isWithinTapTime = ev.getEventTime() - ev.getDownTime() <= mTimeForLongPress;
                 if (isWithinTapTime && !swipeUpOnBubbleHandle && !mPassedTouchSlop
                         && mStashedOrCollapsedOnDown) {
                     // Taps on the handle / collapsed state should open the bar
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index 9d8fc4f..dd83af6 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -191,6 +191,7 @@
                         recentsViewData = inject(),
                         recentTasksRepository = inject(),
                         getThumbnailPositionUseCase = inject(),
+                        dispatcherProvider = inject(),
                     )
                 }
                 GetThumbnailUseCase::class.java -> GetThumbnailUseCase(taskRepository = inject())
diff --git a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
index e6c8d27..0f61b95 100644
--- a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
+++ b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
@@ -74,6 +74,7 @@
                 recentsViewData = RecentsDependencies.get(),
                 getThumbnailPositionUseCase = RecentsDependencies.get(),
                 recentTasksRepository = RecentsDependencies.get(),
+                dispatcherProvider = RecentsDependencies.get(),
             )
         viewModel.overlayState
             .onEach {
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
index 14359db..81a904b 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.quickstep.task.viewmodel
 
 import android.graphics.Matrix
+import com.android.launcher3.util.coroutines.DispatcherProvider
 import com.android.quickstep.recents.data.RecentTasksRepository
 import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
@@ -27,6 +28,7 @@
 import com.android.systemui.shared.recents.model.Task
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
 /** View model for TaskOverlay */
@@ -35,11 +37,14 @@
     recentsViewData: RecentsViewData,
     private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
     recentTasksRepository: RecentTasksRepository,
+    dispatcherProvider: DispatcherProvider,
 ) {
     val overlayState =
         combine(
                 recentsViewData.overlayEnabled,
-                recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) },
+                recentsViewData.settledFullyVisibleTaskIds
+                    .map { it.contains(task.key.id) }
+                    .distinctUntilChanged(),
                 recentTasksRepository.getThumbnailById(task.key.id),
             ) { isOverlayEnabled, isFullyVisible, thumbnailData ->
                 if (isOverlayEnabled && isFullyVisible) {
@@ -52,6 +57,7 @@
                 }
             }
             .distinctUntilChanged()
+            .flowOn(dispatcherProvider.background)
 
     fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
         val matrix: Matrix
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index 2f773b3..46b5659 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -245,9 +245,11 @@
             animator.onStashStateChangingWhileAnimating()
         }
 
-        // The physics animation test util posts the cancellation to the looper thread, so we have
-        // to wait again and let it finish.
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+        // wait for the animation to cancel
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+            handleAnimator,
+            DynamicAnimation.TRANSLATION_Y,
+        )
 
         // verify that the hide animation was canceled
         assertThat(animatorScheduler.delayedBlock).isNull()
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt
index 2e91f5c..95504af 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt
@@ -22,6 +22,7 @@
 import android.graphics.Color
 import android.graphics.Matrix
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.TestDispatcherProvider
 import com.android.quickstep.recents.data.FakeTasksRepository
 import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
@@ -33,7 +34,10 @@
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,6 +45,7 @@
 import org.mockito.kotlin.whenever
 
 /** Test for [TaskOverlayViewModel] */
+@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class TaskOverlayViewModelTest {
     private val task =
@@ -58,104 +63,123 @@
     private val recentsViewData = RecentsViewData()
     private val tasksRepository = FakeTasksRepository()
     private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
     private val systemUnderTest =
-        TaskOverlayViewModel(task, recentsViewData, mGetThumbnailPositionUseCase, tasksRepository)
+        TaskOverlayViewModel(
+            task,
+            recentsViewData,
+            mGetThumbnailPositionUseCase,
+            tasksRepository,
+            TestDispatcherProvider(dispatcher),
+        )
 
     @Test
-    fun initialStateIsDisabled() = runTest {
-        assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
-    }
+    fun initialStateIsDisabled() =
+        testScope.runTest { assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled) }
 
     @Test
-    fun recentsViewOverlayDisabled_Disabled() = runTest {
-        recentsViewData.overlayEnabled.value = false
-        recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
+    fun recentsViewOverlayDisabled_Disabled() =
+        testScope.runTest {
+            recentsViewData.overlayEnabled.value = false
+            recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
 
-        assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
-    }
+            assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
+        }
 
     @Test
-    fun taskNotFullyVisible_Disabled() = runTest {
-        recentsViewData.overlayEnabled.value = true
-        recentsViewData.settledFullyVisibleTaskIds.value = setOf()
+    fun taskNotFullyVisible_Disabled() =
+        testScope.runTest {
+            recentsViewData.overlayEnabled.value = true
+            recentsViewData.settledFullyVisibleTaskIds.value = setOf()
 
-        assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
-    }
+            assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
+        }
 
     @Test
-    fun noThumbnail_Enabled() = runTest {
-        recentsViewData.overlayEnabled.value = true
-        recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
-        task.isLocked = false
+    fun noThumbnail_Enabled() =
+        testScope.runTest {
+            recentsViewData.overlayEnabled.value = true
+            recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
+            task.isLocked = false
 
-        assertThat(systemUnderTest.overlayState.first())
-            .isEqualTo(Enabled(isRealSnapshot = false, thumbnail = null))
-    }
+            assertThat(systemUnderTest.overlayState.first())
+                .isEqualTo(Enabled(isRealSnapshot = false, thumbnail = null))
+        }
 
     @Test
-    fun withThumbnail_RealSnapshot_NotLocked_Enabled() = runTest {
-        recentsViewData.overlayEnabled.value = true
-        recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
-        tasksRepository.seedTasks(listOf(task))
-        tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
-        tasksRepository.setVisibleTasks(setOf(TASK_ID))
-        thumbnailData.isRealSnapshot = true
-        task.isLocked = false
+    fun withThumbnail_RealSnapshot_NotLocked_Enabled() =
+        testScope.runTest {
+            recentsViewData.overlayEnabled.value = true
+            recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
+            tasksRepository.seedTasks(listOf(task))
+            tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
+            tasksRepository.setVisibleTasks(setOf(TASK_ID))
+            thumbnailData.isRealSnapshot = true
+            task.isLocked = false
 
-        assertThat(systemUnderTest.overlayState.first())
-            .isEqualTo(Enabled(isRealSnapshot = true, thumbnail = thumbnailData.thumbnail))
-    }
+            assertThat(systemUnderTest.overlayState.first())
+                .isEqualTo(Enabled(isRealSnapshot = true, thumbnail = thumbnailData.thumbnail))
+        }
 
     @Test
-    fun withThumbnail_RealSnapshot_Locked_Enabled() = runTest {
-        recentsViewData.overlayEnabled.value = true
-        recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
-        tasksRepository.seedTasks(listOf(task))
-        tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
-        tasksRepository.setVisibleTasks(setOf(TASK_ID))
-        thumbnailData.isRealSnapshot = true
-        task.isLocked = true
+    fun withThumbnail_RealSnapshot_Locked_Enabled() =
+        testScope.runTest {
+            recentsViewData.overlayEnabled.value = true
+            recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
+            tasksRepository.seedTasks(listOf(task))
+            tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
+            tasksRepository.setVisibleTasks(setOf(TASK_ID))
+            thumbnailData.isRealSnapshot = true
+            task.isLocked = true
 
-        assertThat(systemUnderTest.overlayState.first())
-            .isEqualTo(Enabled(isRealSnapshot = false, thumbnail = thumbnailData.thumbnail))
-    }
+            assertThat(systemUnderTest.overlayState.first())
+                .isEqualTo(Enabled(isRealSnapshot = false, thumbnail = thumbnailData.thumbnail))
+        }
 
     @Test
-    fun withThumbnail_FakeSnapshot_Enabled() = runTest {
-        recentsViewData.overlayEnabled.value = true
-        recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
-        tasksRepository.seedTasks(listOf(task))
-        tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
-        tasksRepository.setVisibleTasks(setOf(TASK_ID))
-        thumbnailData.isRealSnapshot = false
-        task.isLocked = false
+    fun withThumbnail_FakeSnapshot_Enabled() =
+        testScope.runTest {
+            recentsViewData.overlayEnabled.value = true
+            recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
+            tasksRepository.seedTasks(listOf(task))
+            tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
+            tasksRepository.setVisibleTasks(setOf(TASK_ID))
+            thumbnailData.isRealSnapshot = false
+            task.isLocked = false
 
-        assertThat(systemUnderTest.overlayState.first())
-            .isEqualTo(Enabled(isRealSnapshot = false, thumbnail = thumbnailData.thumbnail))
-    }
+            assertThat(systemUnderTest.overlayState.first())
+                .isEqualTo(Enabled(isRealSnapshot = false, thumbnail = thumbnailData.thumbnail))
+        }
 
     @Test
-    fun getThumbnailMatrix_MissingThumbnail() = runTest {
-        val isRtl = true
+    fun getThumbnailMatrix_MissingThumbnail() =
+        testScope.runTest {
+            val isRtl = true
 
-        whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
-            .thenReturn(MissingThumbnail)
+            whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+                .thenReturn(MissingThumbnail)
 
-        assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
-            .isEqualTo(ThumbnailPositionState(Matrix.IDENTITY_MATRIX, isRotated = false))
-    }
+            assertThat(
+                    systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl)
+                )
+                .isEqualTo(ThumbnailPositionState(Matrix.IDENTITY_MATRIX, isRotated = false))
+        }
 
     @Test
-    fun getThumbnailMatrix_MatrixScaling() = runTest {
-        val isRtl = true
-        val isRotated = true
+    fun getThumbnailMatrix_MatrixScaling() =
+        testScope.runTest {
+            val isRtl = true
+            val isRotated = true
 
-        whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
-            .thenReturn(MatrixScaling(MATRIX, isRotated))
+            whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+                .thenReturn(MatrixScaling(MATRIX, isRotated))
 
-        assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
-            .isEqualTo(ThumbnailPositionState(MATRIX, isRotated))
-    }
+            assertThat(
+                    systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl)
+                )
+                .isEqualTo(ThumbnailPositionState(MATRIX, isRotated))
+        }
 
     companion object {
         const val TASK_ID = 0