More test for taskbar overflow

Adds tests to TaskbarOverflowTests to verify that:
*   tapping overflow view toggles keyboard quick switch view
*   tasks associated with a hotseat item don't get added to
    recents taskbar section / overflow view
*   keyboard quick switch view shown from taskbar contains
    running tasks, excluding tasks associated with a hotseat item

Bug: 379774843
Flag: com.android.launcher3.taskbar_overflow
Test: atest TaskbarOverflowTest

Change-Id: Iee6316c33cef6322d567e853f9fa358b7af9e172
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 5afc5ed..8555376 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.Flags.enableAltTabKqsOnConnectedDisplays;
 
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.view.MotionEvent;
@@ -354,6 +355,27 @@
         }
     }
 
+    @VisibleForTesting
+    boolean isShownFromTaskbar() {
+        return isShown() && mQuickSwitchViewController.wasOpenedFromTaskbar();
+    }
+
+    @VisibleForTesting
+    boolean isShown() {
+        return mQuickSwitchViewController != null
+                && !mQuickSwitchViewController.isCloseAnimationRunning();
+    }
+
+    @VisibleForTesting
+    List<Integer> shownTaskIds() {
+        if (!isShown()) {
+            return Collections.emptyList();
+        }
+
+        return mTasks.stream().flatMap(
+                groupTask -> groupTask.getTasks().stream().map(task -> task.key.id)).toList();
+    }
+
     @Override
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "KeyboardQuickSwitchController:");
@@ -423,7 +445,13 @@
             if (task == null) {
                 return false;
             }
-            int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
+            ActivityManager.RunningTaskInfo runningTaskInfo =
+                    ActivityManagerWrapper.getInstance().getRunningTask();
+            if (runningTaskInfo == null) {
+                return false;
+            }
+
+            int runningTaskId = runningTaskInfo.taskId;
             return task.containsTask(runningTaskId);
         }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index 3a27bb1..3761044 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -19,6 +19,7 @@
 import android.animation.AnimatorTestRule
 import android.content.ComponentName
 import android.content.Intent
+import android.os.Process
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
@@ -27,6 +28,7 @@
 import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
 import com.android.launcher3.R
 import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
 import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
@@ -111,6 +113,7 @@
     @InjectController lateinit var recentAppsController: TaskbarRecentAppsController
     @InjectController lateinit var bubbleBarViewController: BubbleBarViewController
     @InjectController lateinit var bubbleStashController: BubbleStashController
+    @InjectController lateinit var keyboardQuickSwitchController: KeyboardQuickSwitchController
 
     private var desktopTaskListener: IDesktopTaskListener? = null
 
@@ -209,8 +212,10 @@
         runOnMainSync {
             val taskbarView: TaskbarView =
                 taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+            val hotseatItems = createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount)
+
             taskbarView.updateItems(
-                createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount),
+                recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
                 recentAppsController.shownTasks,
             )
         }
@@ -327,13 +332,122 @@
         assertThat(taskbarIconsCentered).isTrue()
     }
 
+    @Test
+    @TaskbarMode(PINNED)
+    fun testPressingOverflowButtonOpensKeyboardQuickSwitch() {
+        val maxNumIconViews = maxNumberOfTaskbarIcons
+        // Assume there are at least all apps and divider icon, as they would appear once running
+        // apps are added, even if not present initially.
+        val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+
+        val targetOverflowSize = 5
+        val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
+        createDesktopTask(createdTasks)
+
+        assertThat(taskbarOverflowIconIndex).isEqualTo(initialIconCount)
+        tapOverflowIcon()
+        // Keyboard quick switch view is shown only after list of recent task is asynchronously
+        // retrieved from the recents model.
+        runOnMainSync { recentsModel.resolvePendingTaskRequests() }
+
+        assertThat(getOnUiThread { keyboardQuickSwitchController.isShownFromTaskbar }).isTrue()
+        assertThat(getOnUiThread { keyboardQuickSwitchController.shownTaskIds() })
+            .containsExactlyElementsIn(0..<createdTasks)
+
+        tapOverflowIcon()
+        assertThat(keyboardQuickSwitchController.isShown).isFalse()
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
+    fun testHotseatItemTasksNotShownInRecents() {
+        val maxNumIconViews = maxNumberOfTaskbarIcons
+        // Assume there are at least all apps and divider icon, as they would appear once running
+        // apps are added, even if not present initially.
+        val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+        val hotseatItems = createHotseatItems(1)
+
+        val targetOverflowSize = 5
+        val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
+        createDesktopTaskWithTasksFromPackages(
+            listOf("fake") +
+                listOf(hotseatItems[0]?.targetPackage ?: "") +
+                List(createdTasks - 2) { "fake" }
+        )
+
+        runOnMainSync {
+            val taskbarView: TaskbarView =
+                taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+            taskbarView.updateItems(
+                recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
+                recentAppsController.shownTasks,
+            )
+        }
+
+        assertThat(maxNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+        assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+        assertThat(taskbarOverflowIconIndex).isEqualTo(initialIconCount + hotseatItems.size)
+        assertThat(overflowItems)
+            .containsExactlyElementsIn(listOf(0) + (2..targetOverflowSize + 1).toList())
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
+    fun testHotseatItemTasksNotShownInKQS() {
+        val maxNumIconViews = maxNumberOfTaskbarIcons
+        // Assume there are at least all apps and divider icon, as they would appear once running
+        // apps are added, even if not present initially.
+        val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+        val hotseatItems = createHotseatItems(1)
+
+        val targetOverflowSize = 5
+        val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
+        createDesktopTaskWithTasksFromPackages(
+            listOf("fake") +
+                listOf(hotseatItems[0]?.targetPackage ?: "") +
+                List(createdTasks - 2) { "fake" }
+        )
+
+        runOnMainSync {
+            val taskbarView: TaskbarView =
+                taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+            taskbarView.updateItems(
+                recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
+                recentAppsController.shownTasks,
+            )
+        }
+
+        tapOverflowIcon()
+        // Keyboard quick switch view is shown only after list of recent task is asynchronously
+        // retrieved from the recents model.
+        runOnMainSync { recentsModel.resolvePendingTaskRequests() }
+
+        assertThat(getOnUiThread { keyboardQuickSwitchController.isShownFromTaskbar }).isTrue()
+        assertThat(getOnUiThread { keyboardQuickSwitchController.shownTaskIds() })
+            .containsExactlyElementsIn(listOf(0) + (2..<createdTasks).toList())
+    }
+
     private fun createDesktopTask(tasksToAdd: Int) {
+        createDesktopTaskWithTasksFromPackages((0..<tasksToAdd).map { "fake" })
+    }
+
+    private fun createDesktopTaskWithTasksFromPackages(packages: List<String>) {
         val tasks =
-            (0..<tasksToAdd).map {
-                Task(Task.TaskKey(it, 0, Intent(), ComponentName("", ""), 0, 2000))
-            }
+            packages.mapIndexed({ index, p ->
+                Task(
+                    Task.TaskKey(
+                        index,
+                        0,
+                        Intent().apply { `package` = p },
+                        ComponentName(p, ""),
+                        Process.myUserHandle().identifier,
+                        2000,
+                    )
+                )
+            })
+
         recentsModel.updateRecentTasks(listOf(DesktopTask(deskId = 0, tasks)))
-        for (task in 1..tasksToAdd) {
+        for (task in 1..tasks.size) {
             desktopTaskListener?.onTasksVisibilityChanged(
                 context.virtualDisplay.display.displayId,
                 task,
@@ -394,6 +508,14 @@
             }
         }
 
+    private fun tapOverflowIcon() {
+        runOnMainSync {
+            val overflowIcon =
+                taskbarViewController.iconViews.firstOrNull { it is TaskbarOverflowView }
+            assertThat(overflowIcon?.callOnClick()).isTrue()
+        }
+    }
+
     /**
      * Adds enough running apps for taskbar to enter overflow of `targetOverflowSize`, and verifies
      * * max number of icons in the taskbar remains unchanged
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
index a7bfa9a..5f7b360 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
@@ -19,6 +19,7 @@
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.RecentsModel.RecentTasksChangedListener
 import com.android.quickstep.TaskIconCache
+import com.android.quickstep.TaskThumbnailCache
 import com.android.quickstep.util.GroupTask
 import java.util.function.Consumer
 import org.mockito.kotlin.any
@@ -27,9 +28,11 @@
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
 
-/** Helper class to mock the {@link RecentsModel} object in test */
+/** Helper class to mock the [RecentsModel] object in test */
 class MockedRecentsModelHelper {
     private val mockIconCache: TaskIconCache = mock()
+    private val mockThumbnailCache: TaskThumbnailCache = mock()
+
     var taskListId = 0
     var recentTasksChangedListener: RecentTasksChangedListener? = null
     var taskRequests: MutableList<(List<GroupTask>) -> Unit> = mutableListOf()
@@ -37,6 +40,8 @@
     val mockRecentsModel: RecentsModel = mock {
         on { iconCache } doReturn mockIconCache
 
+        on { thumbnailCache } doReturn mockThumbnailCache
+
         on { unregisterRecentTasksChangedListener() } doAnswer { recentTasksChangedListener = null }
 
         on { registerRecentTasksChangedListener(any<RecentTasksChangedListener>()) } doAnswer