Merge "Show full page view if categorized view is low density in large screen" into main
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 253f160..1166cf7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -95,6 +95,7 @@
 import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.TaskItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
@@ -1132,6 +1133,11 @@
                 mControllers.uiController.onTaskbarIconLaunched(api);
                 mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
             }
+        } else if (tag instanceof TaskItemInfo info) {
+            UI_HELPER_EXECUTOR.execute(() ->
+                    SystemUiProxy.INSTANCE.get(this).showDesktopApp(info.getTaskId()));
+            mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(
+                    /* stash= */ true);
         } else if (tag instanceof WorkspaceItemInfo) {
             // Tapping a launchable icon on Taskbar
             WorkspaceItemInfo info = (WorkspaceItemInfo) tag;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 0b7ae39..5024cd8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -196,26 +196,26 @@
         final TaskbarRecentAppsController recentAppsController =
                 mControllers.taskbarRecentAppsController;
         hotseatItemInfos = recentAppsController.updateHotseatItemInfos(hotseatItemInfos);
-        Set<String> runningPackages = recentAppsController.getRunningAppPackages();
-        Set<String> minimizedPackages = recentAppsController.getMinimizedAppPackages();
+        Set<Integer> runningTaskIds = recentAppsController.getRunningTaskIds();
+        Set<Integer> minimizedTaskIds = recentAppsController.getMinimizedTaskIds();
 
         if (mDeferUpdatesForSUW) {
             ItemInfo[] finalHotseatItemInfos = hotseatItemInfos;
             mDeferredUpdates = () ->
                     commitHotseatItemUpdates(finalHotseatItemInfos,
-                            recentAppsController.getShownTasks(), runningPackages,
-                            minimizedPackages);
+                            recentAppsController.getShownTasks(), runningTaskIds,
+                            minimizedTaskIds);
         } else {
             commitHotseatItemUpdates(hotseatItemInfos,
-                    recentAppsController.getShownTasks(), runningPackages, minimizedPackages);
+                    recentAppsController.getShownTasks(), runningTaskIds, minimizedTaskIds);
         }
     }
 
     private void commitHotseatItemUpdates(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks,
-            Set<String> runningPackages, Set<String> minimizedPackages) {
+            Set<Integer> runningTaskIds, Set<Integer> minimizedTaskIds) {
         mContainer.updateHotseatItems(hotseatItemInfos, recentTasks);
         mControllers.taskbarViewController.updateIconViewsRunningStates(
-                runningPackages, minimizedPackages);
+                runningTaskIds, minimizedTaskIds);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 36828a8..5c08116 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -18,6 +18,8 @@
 import androidx.annotation.VisibleForTesting
 import com.android.launcher3.Flags.enableRecentsInTaskbar
 import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.TaskItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.statehandlers.DesktopVisibilityController
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
 import com.android.launcher3.util.CancellableTask
@@ -58,9 +60,13 @@
     // Initialized in init.
     private lateinit var controllers: TaskbarControllers
 
-    private var shownHotseatItems: List<ItemInfo> = emptyList()
+    var shownHotseatItems: List<ItemInfo> = emptyList()
+        private set
+
     private var allRecentTasks: List<GroupTask> = emptyList()
     private var desktopTask: DesktopTask? = null
+    // Keeps track of the order in which running tasks appear.
+    private var orderedRunningTaskIds = emptyList<Int>()
     var shownTasks: List<GroupTask> = emptyList()
         private set
 
@@ -70,9 +76,9 @@
     private val isInDesktopMode: Boolean
         get() = desktopVisibilityController?.areDesktopTasksVisible() ?: false
 
-    val runningAppPackages: Set<String>
+    val runningTaskIds: Set<Int>
         /**
-         * Returns the package names of apps that should be indicated as "running" to the user.
+         * Returns the task IDs of apps that should be indicated as "running" to the user.
          * Specifically, we return all the open tasks if we are in Desktop mode, else emptySet().
          */
         get() {
@@ -80,22 +86,19 @@
                 return emptySet()
             }
             val tasks = desktopTask?.tasks ?: return emptySet()
-            return tasks.map { task -> task.key.packageName }.toSet()
+            return tasks.map { task -> task.key.id }.toSet()
         }
 
-    val minimizedAppPackages: Set<String>
+    val minimizedTaskIds: Set<Int>
         /**
-         * Returns the package names of apps that should be indicated as "minimized" to the user.
-         * Specifically, we return all the running packages where all the tasks in that package are
-         * minimized (not visible).
+         * Returns the task IDs for the tasks that should be indicated as "minimized" to the user.
          */
         get() {
             if (!canShowRunningApps || !isInDesktopMode) {
                 return emptySet()
             }
             val desktopTasks = desktopTask?.tasks ?: return emptySet()
-            val packageToTasks = desktopTasks.groupBy { it.key.packageName }
-            return packageToTasks.filterValues { tasks -> tasks.all { !it.isVisible } }.keys
+            return desktopTasks.filter { !it.isVisible }.map { task -> task.key.id }.toSet()
         }
 
     private val recentTasksChangedListener =
@@ -137,25 +140,39 @@
                 .filter { itemInfo -> !itemInfo.isPredictedItem }
                 .toMutableList()
 
+        if (isInDesktopMode && canShowRunningApps) {
+            shownHotseatItems =
+                updateHotseatItemsFromRunningTasks(
+                    getOrderedAndWrappedDesktopTasks(),
+                    shownHotseatItems
+                )
+        }
+
         onRecentsOrHotseatChanged()
 
         return shownHotseatItems.toTypedArray()
     }
 
+    private fun getOrderedAndWrappedDesktopTasks(): List<GroupTask> {
+        val tasks = desktopTask?.tasks ?: emptyList()
+        // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
+        val orderFromId = orderedRunningTaskIds.withIndex().associate { (index, id) -> id to index }
+        val sortedTasks = tasks.sortedWith(compareBy(nullsLast()) { orderFromId[it.key.id] })
+        return sortedTasks.map { GroupTask(it) }
+    }
+
     private fun reloadRecentTasksIfNeeded() {
         if (!recentsModel.isTaskListValid(taskListChangeId)) {
             taskListChangeId =
                 recentsModel.getTasks { tasks ->
                     allRecentTasks = tasks
-                    val oldRunningPackages = runningAppPackages
-                    val oldMinimizedPackages = minimizedAppPackages
+                    val oldRunningTaskdIds = runningTaskIds
+                    val oldMinimizedTaskIds = minimizedTaskIds
                     desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
-                    val runningPackagesChanged = oldRunningPackages != runningAppPackages
-                    val minimizedPackagessChanged = oldMinimizedPackages != minimizedAppPackages
+                    val runningTasksChanged = oldRunningTaskdIds != runningTaskIds
+                    val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds
                     if (
-                        onRecentsOrHotseatChanged() ||
-                            runningPackagesChanged ||
-                            minimizedPackagessChanged
+                        onRecentsOrHotseatChanged() || runningTasksChanged || minimizedTasksChanged
                     ) {
                         controllers.taskbarViewController.commitRunningAppsToUI()
                     }
@@ -170,6 +187,7 @@
      */
     private fun onRecentsOrHotseatChanged(): Boolean {
         val oldShownTasks = shownTasks
+        orderedRunningTaskIds = updateOrderedRunningTaskIds()
         shownTasks =
             if (isInDesktopMode) {
                 computeShownRunningTasks()
@@ -201,22 +219,39 @@
         return shownTasksChanged
     }
 
+    private fun updateOrderedRunningTaskIds(): MutableList<Int> {
+        val desktopTaskAsList = getOrderedAndWrappedDesktopTasks()
+        val desktopTaskIds = desktopTaskAsList.map { it.task1.key.id }
+        var newOrder =
+            orderedRunningTaskIds
+                .filter { it in desktopTaskIds } // Only keep the tasks that are still running
+                .toMutableList()
+        // Add new tasks not already listed
+        newOrder.addAll(desktopTaskIds.filter { it !in newOrder })
+        return newOrder
+    }
+
     private fun computeShownRunningTasks(): List<GroupTask> {
         if (!canShowRunningApps) {
             return emptyList()
         }
-        val tasks = desktopTask?.tasks ?: emptyList()
-        // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
-        var desktopTaskAsList = tasks.map { GroupTask(it) }
-        // TODO(b/315344726 Multi-instance support): dedupe Tasks of the same package too.
-        desktopTaskAsList = dedupeHotseatTasks(desktopTaskAsList, shownHotseatItems)
-        val desktopPackages = desktopTaskAsList.map { it.packageNames }
-        // Remove any missing Tasks.
-        val newShownTasks = shownTasks.filter { it.packageNames in desktopPackages }.toMutableList()
-        val newShownPackages = newShownTasks.map { it.packageNames }
+        val desktopTaskAsList = getOrderedAndWrappedDesktopTasks()
+        val desktopTaskIds = desktopTaskAsList.map { it.task1.key.id }
+        val shownTaskIds = shownTasks.map { it.task1.key.id }
+        // TODO(b/315344726 Multi-instance support): only show one icon per package once we support
+        //  taskbar multi-instance menus
+        val shownHotseatItemTaskIds =
+            shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
+        // Remove any newly-missing Tasks, and actual group-tasks
+        val newShownTasks =
+            shownTasks
+                .filter { !it.hasMultipleTasks() }
+                .filter { it.task1.key.id in desktopTaskIds }
+                .toMutableList()
         // Add any new Tasks, maintaining the order from previous shownTasks.
-        newShownTasks.addAll(desktopTaskAsList.filter { it.packageNames !in newShownPackages })
-        return newShownTasks.toList()
+        newShownTasks.addAll(desktopTaskAsList.filter { it.task1.key.id !in shownTaskIds })
+        // Remove any tasks already covered by Hotseat icons
+        return newShownTasks.filter { it.task1.key.id !in shownHotseatItemTaskIds }
     }
 
     private fun computeShownRecentTasks(): List<GroupTask> {
@@ -245,6 +280,25 @@
         }
     }
 
+    /**
+     * Returns the hotseat items updated so that any item that points to a package with a running
+     * task also references that task.
+     */
+    private fun updateHotseatItemsFromRunningTasks(
+        groupTasks: List<GroupTask>,
+        shownHotseatItems: List<ItemInfo>
+    ): List<ItemInfo> =
+        shownHotseatItems.map { itemInfo ->
+            if (itemInfo is TaskItemInfo) {
+                itemInfo
+            } else {
+                val foundTask =
+                    groupTasks.find { task -> task.task1.key.packageName == itemInfo.targetPackage }
+                        ?: return@map itemInfo
+                TaskItemInfo(foundTask.task1.key.id, itemInfo as WorkspaceItemInfo)
+            }
+        }
+
     override fun dumpLogs(prefix: String, pw: PrintWriter) {
         pw.println("$prefix TaskbarRecentAppsController:")
         pw.println("$prefix\tcanShowRunningApps=$canShowRunningApps")
@@ -253,8 +307,8 @@
         pw.println("$prefix\tallRecentTasks=${allRecentTasks.map { it.packageNames }}")
         pw.println("$prefix\tdesktopTask=${desktopTask?.packageNames}")
         pw.println("$prefix\tshownTasks=${shownTasks.map { it.packageNames }}")
-        pw.println("$prefix\trunningTasks=$runningAppPackages")
-        pw.println("$prefix\tminimizedTasks=$minimizedAppPackages")
+        pw.println("$prefix\trunningTaskIds=$runningTaskIds")
+        pw.println("$prefix\tminimizedTaskIds=$minimizedTaskIds")
     }
 
     private val GroupTask.packageNames: List<String>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index e59a016..527e3a3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -63,6 +63,7 @@
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.TaskItemInfo;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
@@ -515,35 +516,38 @@
         return mTaskbarView.getTaskbarDividerView();
     }
 
-    /** Updates which icons are marked as running given the Set of currently running packages. */
-    public void updateIconViewsRunningStates(Set<String> runningPackages,
-            Set<String> minimizedPackages) {
+    /**
+     * Updates which icons are marked as running or minimized given the Sets of currently running
+     * and minimized tasks.
+     */
+    public void updateIconViewsRunningStates(Set<Integer> runningTaskIds,
+            Set<Integer> minimizedTaskIds) {
         for (View iconView : getIconViews()) {
             if (iconView instanceof BubbleTextView btv) {
                 btv.updateRunningState(
-                        getRunningAppState(btv, runningPackages, minimizedPackages));
+                        getRunningAppState(btv, runningTaskIds, minimizedTaskIds));
             }
         }
     }
 
     private BubbleTextView.RunningAppState getRunningAppState(
             BubbleTextView btv,
-            Set<String> runningPackages,
-            Set<String> minimizedPackages) {
+            Set<Integer> runningTaskIds,
+            Set<Integer> minimizedTaskIds) {
         Object tag = btv.getTag();
-        if (tag instanceof ItemInfo itemInfo) {
-            if (minimizedPackages.contains(itemInfo.getTargetPackage())) {
+        if (tag instanceof TaskItemInfo itemInfo) {
+            if (minimizedTaskIds.contains(itemInfo.getTaskId())) {
                 return BubbleTextView.RunningAppState.MINIMIZED;
             }
-            if (runningPackages.contains(itemInfo.getTargetPackage())) {
+            if (runningTaskIds.contains(itemInfo.getTaskId())) {
                 return BubbleTextView.RunningAppState.RUNNING;
             }
         }
         if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
-            if (minimizedPackages.contains(groupTask.task1.key.getPackageName())) {
+            if (minimizedTaskIds.contains(groupTask.task1.key.id)) {
                 return BubbleTextView.RunningAppState.MINIMIZED;
             }
-            if (runningPackages.contains(groupTask.task1.key.getPackageName())) {
+            if (runningTaskIds.contains(groupTask.task1.key.id)) {
                 return BubbleTextView.RunningAppState.RUNNING;
             }
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 402b091..9be898d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -979,8 +979,7 @@
         return translationX - getScaleIconShift();
     }
 
-    private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount,
-            boolean onLeft) {
+    private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount, boolean onLeft) {
         if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
             return 0;
         }
@@ -991,7 +990,9 @@
                     bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED
                             ? mIconOverlapAmount : 0);
         } else {
-            translationX = mBubbleBarPadding + (bubbleIndex == 0 ? 0 : mIconOverlapAmount);
+            translationX = mBubbleBarPadding + (
+                    bubbleIndex == 0 || bubbleCount <= MAX_VISIBLE_BUBBLES_COLLAPSED
+                            ? 0 : mIconOverlapAmount);
         }
         return translationX - getScaleIconShift();
     }
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index 13c4f72..27e761a 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -26,6 +26,7 @@
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
 import com.android.launcher3.model.data.AppInfo
 import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.TaskItemInfo
 import com.android.launcher3.statehandlers.DesktopVisibilityController
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.RecentsModel.RecentTasksChangedListener
@@ -78,6 +79,13 @@
         val listenerCaptor = ArgumentCaptor.forClass(RecentTasksChangedListener::class.java)
         verify(mockRecentsModel).registerRecentTasksChangedListener(listenerCaptor.capture())
         recentTasksChangedListener = listenerCaptor.value
+
+        // Make sure updateHotseatItemInfos() is called after commitRunningAppsToUI()
+        whenever(taskbarViewController.commitRunningAppsToUI()).then {
+            recentAppsController.updateHotseatItemInfos(
+                recentAppsController.shownHotseatItems.toTypedArray()
+            )
+        }
     }
 
     @Test
@@ -88,7 +96,7 @@
         val newHotseatItems =
             prepareHotseatAndRunningAndRecentApps(
                 hotseatPackages = hotseatPackages,
-                runningTaskPackages = emptyList(),
+                runningTasks = emptyList(),
                 recentTaskPackages = emptyList()
             )
         assertThat(newHotseatItems.map { it?.targetPackage })
@@ -103,7 +111,7 @@
         val newHotseatItems =
             prepareHotseatAndRunningAndRecentApps(
                 hotseatPackages = hotseatPackages,
-                runningTaskPackages = emptyList(),
+                runningTasks = emptyList(),
                 recentTaskPackages = emptyList()
             )
         assertThat(newHotseatItems.map { it?.targetPackage })
@@ -117,7 +125,7 @@
         val newHotseatItems =
             prepareHotseatAndRunningAndRecentApps(
                 hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
-                runningTaskPackages = emptyList(),
+                runningTasks = emptyList(),
                 recentTaskPackages = emptyList()
             )
         val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
@@ -126,13 +134,58 @@
     }
 
     @Test
+    fun updateHotseatItemInfos_inDesktopMode_hotseatPackageHasRunningTask_hotseatItemLinksToTask() {
+        setInDesktopMode(true)
+
+        val newHotseatItems =
+            prepareHotseatAndRunningAndRecentApps(
+                hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
+                runningTasks = listOf(createTask(id = 1, HOTSEAT_PACKAGE_1)),
+                recentTaskPackages = emptyList()
+            )
+
+        assertThat(newHotseatItems).hasLength(2)
+        assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
+        assertThat(newHotseatItems[1]).isNotInstanceOf(TaskItemInfo::class.java)
+        val hotseatItem1 = newHotseatItems[0] as TaskItemInfo
+        assertThat(hotseatItem1.taskId).isEqualTo(1)
+    }
+
+    @Test
+    fun updateHotseatItemInfos_inDesktopMode_twoRunningTasksSamePackage_hotseatCoversFirstTask() {
+        setInDesktopMode(true)
+
+        val newHotseatItems =
+            prepareHotseatAndRunningAndRecentApps(
+                hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
+                runningTasks =
+                    listOf(
+                        createTask(id = 1, HOTSEAT_PACKAGE_1),
+                        createTask(id = 2, HOTSEAT_PACKAGE_1)
+                    ),
+                recentTaskPackages = emptyList()
+            )
+
+        // First task is in Hotseat Items
+        assertThat(newHotseatItems).hasLength(2)
+        assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
+        assertThat(newHotseatItems[1]).isNotInstanceOf(TaskItemInfo::class.java)
+        val hotseatItem1 = newHotseatItems[0] as TaskItemInfo
+        assertThat(hotseatItem1.taskId).isEqualTo(1)
+        // Second task is in shownTasks
+        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+        assertThat(shownTasks)
+            .containsExactlyElementsIn(listOf(createTask(id = 2, HOTSEAT_PACKAGE_1)))
+    }
+
+    @Test
     fun updateHotseatItemInfos_canShowRecent_notInDesktopMode_returnsNonPredictedHotseatItems() {
         recentAppsController.canShowRecentApps = true
         setInDesktopMode(false)
         val newHotseatItems =
             prepareHotseatAndRunningAndRecentApps(
                 hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
-                runningTaskPackages = emptyList(),
+                runningTasks = emptyList(),
                 recentTaskPackages = emptyList()
             )
         val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
@@ -146,7 +199,11 @@
         setInDesktopMode(true)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
-            runningTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
+            runningTasks =
+                listOf(
+                    createTask(id = 1, RUNNING_APP_PACKAGE_1),
+                    createTask(id = 2, RUNNING_APP_PACKAGE_2)
+                ),
             recentTaskPackages = emptyList()
         )
         assertThat(recentAppsController.shownTasks).isEmpty()
@@ -158,7 +215,7 @@
         setInDesktopMode(false)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
-            runningTaskPackages = emptyList(),
+            runningTasks = emptyList(),
             recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
         )
         assertThat(recentAppsController.shownTasks).isEmpty()
@@ -169,11 +226,15 @@
         setInDesktopMode(false)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+            runningTasks =
+                listOf(
+                    createTask(id = 1, RUNNING_APP_PACKAGE_1),
+                    createTask(id = 2, RUNNING_APP_PACKAGE_2)
+                ),
             recentTaskPackages = emptyList()
         )
         assertThat(recentAppsController.shownTasks).isEmpty()
-        assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+        assertThat(recentAppsController.minimizedTaskIds).isEmpty()
     }
 
     @Test
@@ -181,7 +242,7 @@
         setInDesktopMode(true)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = emptyList(),
+            runningTasks = emptyList(),
             recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
         )
         assertThat(recentAppsController.shownTasks).isEmpty()
@@ -190,120 +251,161 @@
     @Test
     fun onRecentTasksChanged_inDesktopMode_shownTasks_returnsRunningTasks() {
         setInDesktopMode(true)
-        val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = runningTaskPackages,
+            runningTasks = listOf(task1, task2),
             recentTaskPackages = emptyList()
         )
-        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
-        assertThat(shownPackages).containsExactlyElementsIn(runningTaskPackages)
-    }
-
-    @Test
-    fun onRecentTasksChanged_inDesktopMode_runningAppIsHotseatItem_shownTasks_returnsDistinctItems() {
-        setInDesktopMode(true)
-        prepareHotseatAndRunningAndRecentApps(
-            hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
-            runningTaskPackages =
-                listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
-            recentTaskPackages = emptyList()
-        )
-        val expectedPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
-        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
-        assertThat(shownPackages).containsExactlyElementsIn(expectedPackages)
+        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+        assertThat(shownTasks).containsExactlyElementsIn(listOf(task1, task2))
     }
 
     @Test
     fun onRecentTasksChanged_notInDesktopMode_getRunningApps_returnsEmptySet() {
         setInDesktopMode(false)
+        val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+            runningTasks = listOf(task1, task2),
             recentTaskPackages = emptyList()
         )
-        assertThat(recentAppsController.runningAppPackages).isEmpty()
-        assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+        assertThat(recentAppsController.runningTaskIds).isEmpty()
+        assertThat(recentAppsController.minimizedTaskIds).isEmpty()
     }
 
     @Test
     fun onRecentTasksChanged_inDesktopMode_getRunningApps_returnsAllDesktopTasks() {
         setInDesktopMode(true)
-        val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = runningTaskPackages,
+            runningTasks = listOf(task1, task2),
             recentTaskPackages = emptyList()
         )
-        assertThat(recentAppsController.runningAppPackages)
-            .containsExactlyElementsIn(runningTaskPackages)
-        assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+        assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2))
+        assertThat(recentAppsController.minimizedTaskIds).isEmpty()
     }
 
     @Test
     fun onRecentTasksChanged_inDesktopMode_getRunningApps_includesHotseat() {
         setInDesktopMode(true)
-        val runningTaskPackages =
-            listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        val runningTasks =
+            listOf(
+                createTask(id = 1, HOTSEAT_PACKAGE_1),
+                createTask(id = 2, RUNNING_APP_PACKAGE_1),
+                createTask(id = 3, RUNNING_APP_PACKAGE_2)
+            )
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
-            runningTaskPackages = runningTaskPackages,
+            runningTasks = runningTasks,
             recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
         )
-        assertThat(recentAppsController.runningAppPackages)
-            .containsExactlyElementsIn(runningTaskPackages)
-        assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+        assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2, 3))
+        assertThat(recentAppsController.minimizedTaskIds).isEmpty()
     }
 
     @Test
-    fun getMinimizedApps_inDesktopMode_returnsAllAppsRunningAndInvisibleAppsMinimized() {
+    fun onRecentTasksChanged_inDesktopMode_allAppsRunningAndInvisibleAppsMinimized() {
         setInDesktopMode(true)
-        val runningTaskPackages =
-            listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3)
-        val minimizedTaskIndices = setOf(2) // RUNNING_APP_PACKAGE_3
+        val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
+        val task3Minimized = createTask(id = 3, RUNNING_APP_PACKAGE_3, isVisible = false)
+        val runningTasks = listOf(task1, task2, task3Minimized)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = runningTaskPackages,
-            minimizedTaskIndices = minimizedTaskIndices,
+            runningTasks = runningTasks,
             recentTaskPackages = emptyList()
         )
-        assertThat(recentAppsController.runningAppPackages)
-            .containsExactlyElementsIn(runningTaskPackages)
-        assertThat(recentAppsController.minimizedAppPackages).containsExactly(RUNNING_APP_PACKAGE_3)
+        assertThat(recentAppsController.runningTaskIds).containsExactly(1, 2, 3)
+        assertThat(recentAppsController.minimizedTaskIds).containsExactly(3)
     }
 
     @Test
-    fun getMinimizedApps_inDesktopMode_twoTasksSamePackageOneMinimizedReturnsNotMinimized() {
+    fun onRecentTasksChanged_inDesktopMode_samePackage_differentTasks_severalRunningTasks() {
         setInDesktopMode(true)
-        val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_1)
-        val minimizedTaskIndices = setOf(1) // The second RUNNING_APP_PACKAGE_1 task.
+        val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = runningTaskPackages,
-            minimizedTaskIndices = minimizedTaskIndices,
+            runningTasks = listOf(task1, task2),
             recentTaskPackages = emptyList()
         )
-        assertThat(recentAppsController.runningAppPackages)
-            .containsExactlyElementsIn(runningTaskPackages.toSet())
-        assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+        assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2))
     }
 
     @Test
     fun onRecentTasksChanged_inDesktopMode_shownTasks_maintainsOrder() {
         setInDesktopMode(true)
-        val originalOrder = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = originalOrder,
+            runningTasks = listOf(task1, task2),
             recentTaskPackages = emptyList()
         )
+
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1),
+            runningTasks = listOf(task2, task1),
             recentTaskPackages = emptyList()
         )
-        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
-        assertThat(shownPackages).isEqualTo(originalOrder)
+
+        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+        assertThat(shownTasks).isEqualTo(listOf(task1, task2))
+    }
+
+    @Test
+    fun onRecentTasksChanged_inDesktopMode_multiInstance_shownTasks_maintainsOrder() {
+        setInDesktopMode(true)
+        val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTasks = listOf(task1, task2),
+            recentTaskPackages = emptyList()
+        )
+
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTasks = listOf(task2, task1),
+            recentTaskPackages = emptyList()
+        )
+
+        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+        assertThat(shownTasks).isEqualTo(listOf(task1, task2))
+    }
+
+    @Test
+    fun updateHotseatItems_inDesktopMode_multiInstanceHotseatPackage_shownItems_maintainsOrder() {
+        setInDesktopMode(true)
+        val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
+            runningTasks = listOf(task1, task2),
+            recentTaskPackages = emptyList()
+        )
+        updateRecentTasks( // Trigger a recent-tasks change before calling updateHotseatItems()
+            runningTasks = listOf(task2, task1),
+            recentTaskPackages = emptyList()
+        )
+
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
+            runningTasks = listOf(task2, task1),
+            recentTaskPackages = emptyList()
+        )
+
+        val newHotseatItems = recentAppsController.shownHotseatItems
+        assertThat(newHotseatItems).hasSize(1)
+        assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
+        assertThat((newHotseatItems[0] as TaskItemInfo).taskId).isEqualTo(1)
+        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+        assertThat(shownTasks).isEqualTo(listOf(task2))
     }
 
     @Test
@@ -311,12 +413,12 @@
         setInDesktopMode(false)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = emptyList(),
+            runningTasks = emptyList(),
             recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
         )
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = emptyList(),
+            runningTasks = emptyList(),
             recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1)
         )
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -327,15 +429,17 @@
     @Test
     fun onRecentTasksChanged_inDesktopMode_addTask_shownTasks_maintainsOrder() {
         setInDesktopMode(true)
+        val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
+        val task3 = createTask(id = 3, RUNNING_APP_PACKAGE_3)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+            runningTasks = listOf(task1, task2),
             recentTaskPackages = emptyList()
         )
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages =
-                listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_3),
+            runningTasks = listOf(task2, task1, task3),
             recentTaskPackages = emptyList()
         )
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -349,12 +453,12 @@
         setInDesktopMode(false)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = emptyList(),
+            runningTasks = emptyList(),
             recentTaskPackages = listOf(RECENT_PACKAGE_3, RECENT_PACKAGE_2)
         )
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = emptyList(),
+            runningTasks = emptyList(),
             recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1)
         )
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -365,15 +469,17 @@
     @Test
     fun onRecentTasksChanged_inDesktopMode_removeTask_shownTasks_maintainsOrder() {
         setInDesktopMode(true)
+        val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
+        val task3 = createTask(id = 3, RUNNING_APP_PACKAGE_3)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages =
-                listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3),
+            runningTasks = listOf(task1, task2, task3),
             recentTaskPackages = emptyList()
         )
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1),
+            runningTasks = listOf(task2, task1),
             recentTaskPackages = emptyList()
         )
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -385,12 +491,12 @@
         setInDesktopMode(false)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = emptyList(),
+            runningTasks = emptyList(),
             recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
         )
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = emptyList(),
+            runningTasks = emptyList(),
             recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3)
         )
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -401,27 +507,31 @@
     @Test
     fun onRecentTasksChanged_enterDesktopMode_shownTasks_onlyIncludesRunningTasks() {
         setInDesktopMode(false)
-        val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
         val recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = runningTaskPackages,
+            runningTasks = listOf(runningTask1, runningTask2),
             recentTaskPackages = recentTaskPackages
         )
+
         setInDesktopMode(true)
         recentTasksChangedListener.onRecentTasksChanged()
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
-        assertThat(shownPackages).containsExactlyElementsIn(runningTaskPackages)
+        assertThat(shownPackages).containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
     }
 
     @Test
     fun onRecentTasksChanged_exitDesktopMode_shownTasks_onlyIncludesRecentTasks() {
         setInDesktopMode(true)
-        val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
         val recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = runningTaskPackages,
+            runningTasks = listOf(runningTask1, runningTask2),
             recentTaskPackages = recentTaskPackages
         )
         setInDesktopMode(false)
@@ -437,7 +547,7 @@
         setInDesktopMode(false)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = emptyList(),
+            runningTasks = emptyList(),
             recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
         )
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -449,9 +559,11 @@
     @Test
     fun onRecentTasksChanged_notInDesktopMode_hasRecentAndRunningTasks_shownTasks_returnsRecentTaskAndDesktopTile() {
         setInDesktopMode(false)
+        val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+            runningTasks = listOf(runningTask1, runningTask2),
             recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
         )
         val shownPackages = recentAppsController.shownTasks.map { it.packageNames }
@@ -467,7 +579,7 @@
         setInDesktopMode(false)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = emptyList(),
+            runningTasks = emptyList(),
             recentTaskPackages = listOf(RECENT_SPLIT_PACKAGES_1, RECENT_PACKAGE_1, RECENT_PACKAGE_2)
         )
         val shownPackages = recentAppsController.shownTasks.map { it.packageNames }
@@ -483,14 +595,14 @@
         setInDesktopMode(false)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = emptyList(),
+            runningTasks = emptyList(),
             recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
         )
         verify(taskbarViewController, times(1)).commitRunningAppsToUI()
         // Call onRecentTasksChanged() again with the same tasks, verify it's a no-op.
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = emptyList(),
+            runningTasks = emptyList(),
             recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
         )
         verify(taskbarViewController, times(1)).commitRunningAppsToUI()
@@ -499,16 +611,18 @@
     @Test
     fun onRecentTasksChanged_inDesktopMode_noActualChangeToRunning_commitRunningAppsToUI_notCalled() {
         setInDesktopMode(true)
+        val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+        val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+            runningTasks = listOf(runningTask1, runningTask2),
             recentTaskPackages = emptyList()
         )
         verify(taskbarViewController, times(1)).commitRunningAppsToUI()
         // Call onRecentTasksChanged() again with the same tasks, verify it's a no-op.
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+            runningTasks = listOf(runningTask1, runningTask2),
             recentTaskPackages = emptyList()
         )
         verify(taskbarViewController, times(1)).commitRunningAppsToUI()
@@ -517,21 +631,23 @@
     @Test
     fun onRecentTasksChanged_onlyMinimizedChanges_commitRunningAppsToUI_isCalled() {
         setInDesktopMode(true)
-        val runningTasks = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        val task1Minimized = createTask(id = 1, RUNNING_APP_PACKAGE_1, isVisible = false)
+        val task2Visible = createTask(id = 2, RUNNING_APP_PACKAGE_2)
+        val task2Minimized = createTask(id = 2, RUNNING_APP_PACKAGE_2, isVisible = false)
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = runningTasks,
-            minimizedTaskIndices = setOf(0),
+            runningTasks = listOf(task1Minimized, task2Visible),
             recentTaskPackages = emptyList()
         )
         verify(taskbarViewController, times(1)).commitRunningAppsToUI()
+
         // Call onRecentTasksChanged() again with a new minimized app, verify we update UI.
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
-            runningTaskPackages = runningTasks,
-            minimizedTaskIndices = setOf(0, 1),
+            runningTasks = listOf(task1Minimized, task2Minimized),
             recentTaskPackages = emptyList()
         )
+
         verify(taskbarViewController, times(2)).commitRunningAppsToUI()
     }
 
@@ -539,36 +655,46 @@
     fun onRecentTasksChanged_hotseatAppStartsRunning_commitRunningAppsToUI_isCalled() {
         setInDesktopMode(true)
         val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
+        val originalTasks = listOf(createTask(id = 1, RUNNING_APP_PACKAGE_1))
+        val newTasks =
+            listOf(createTask(id = 1, RUNNING_APP_PACKAGE_1), createTask(id = 2, HOTSEAT_PACKAGE_1))
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = hotseatPackages,
-            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1),
+            runningTasks = originalTasks,
             recentTaskPackages = emptyList()
         )
         verify(taskbarViewController, times(1)).commitRunningAppsToUI()
+
         // Call onRecentTasksChanged() again with a new running app, verify we update UI.
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = hotseatPackages,
-            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, HOTSEAT_PACKAGE_1),
+            runningTasks = newTasks,
             recentTaskPackages = emptyList()
         )
+
         verify(taskbarViewController, times(2)).commitRunningAppsToUI()
     }
 
     private fun prepareHotseatAndRunningAndRecentApps(
         hotseatPackages: List<String>,
-        runningTaskPackages: List<String>,
-        minimizedTaskIndices: Set<Int> = emptySet(),
+        runningTasks: List<Task>,
         recentTaskPackages: List<String>,
     ): Array<ItemInfo?> {
         val hotseatItems = createHotseatItemsFromPackageNames(hotseatPackages)
-        val newHotseatItems =
-            recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
-        val runningTasks = createDesktopTask(runningTaskPackages, minimizedTaskIndices)
+        recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
+        updateRecentTasks(runningTasks, recentTaskPackages)
+        return recentAppsController.shownHotseatItems.toTypedArray()
+    }
+
+    private fun updateRecentTasks(
+        runningTasks: List<Task>,
+        recentTaskPackages: List<String>,
+    ) {
         val recentTasks = createRecentTasksFromPackageNames(recentTaskPackages)
         val allTasks =
             ArrayList<GroupTask>().apply {
-                if (runningTasks != null) {
-                    add(runningTasks)
+                if (!runningTasks.isEmpty()) {
+                    add(DesktopTask(ArrayList(runningTasks)))
                 }
                 addAll(recentTasks)
             }
@@ -580,20 +706,21 @@
             .whenever(mockRecentsModel)
             .getTasks(any<Consumer<List<GroupTask>>>())
         recentTasksChangedListener.onRecentTasksChanged()
-        return newHotseatItems
     }
 
     private fun createHotseatItemsFromPackageNames(packageNames: List<String>): List<ItemInfo> {
-        return packageNames.map {
-            createTestAppInfo(packageName = it).apply {
-                container =
-                    if (it.startsWith("predicted")) {
-                        CONTAINER_HOTSEAT_PREDICTION
-                    } else {
-                        CONTAINER_HOTSEAT
-                    }
+        return packageNames
+            .map {
+                createTestAppInfo(packageName = it).apply {
+                    container =
+                        if (it.startsWith("predicted")) {
+                            CONTAINER_HOTSEAT_PREDICTION
+                        } else {
+                            CONTAINER_HOTSEAT
+                        }
+                }
             }
-        }
+            .map { it.makeWorkspaceItem(taskbarActivityContext) }
     }
 
     private fun createTestAppInfo(
@@ -601,39 +728,24 @@
         className: String = "testClassName"
     ) = AppInfo(ComponentName(packageName, className), className /* title */, userHandle, Intent())
 
-    private fun createDesktopTask(
-        packageNames: List<String>,
-        minimizedTaskIndices: Set<Int>
-    ): DesktopTask? {
-        if (packageNames.isEmpty()) return null
-
-        return DesktopTask(
-            ArrayList(
-                packageNames.mapIndexed { index, packageName ->
-                    createTask(packageName, index !in minimizedTaskIndices)
-                }
-            )
-        )
-    }
-
     private fun createRecentTasksFromPackageNames(packageNames: List<String>): List<GroupTask> {
-        return packageNames.map {
-            if (it.startsWith("split")) {
-                val splitPackages = it.split("_")
+        return packageNames.map { packageName ->
+            if (packageName.startsWith("split")) {
+                val splitPackages = packageName.split("_")
                 GroupTask(
-                    createTask(splitPackages[0]),
-                    createTask(splitPackages[1]),
+                    createTask(100, splitPackages[0]),
+                    createTask(101, splitPackages[1]),
                     /* splitBounds = */ null
                 )
             } else {
-                GroupTask(createTask(it))
+                // Use the number at the end of the test packageName as the id.
+                val id = 1000 + packageName[packageName.length - 1].code
+                GroupTask(createTask(id, packageName))
             }
         }
     }
 
-    private fun createTask(packageName: String, isVisible: Boolean = true): Task {
-        // Use the number at the end of the test packageName as the id.
-        val id = packageName[packageName.length - 1].code
+    private fun createTask(id: Int, packageName: String, isVisible: Boolean = true): Task {
         return Task(
                 Task.TaskKey(
                     id,
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 98ca420..4b38df8 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
 
 import android.content.Context;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -278,6 +279,13 @@
                                     privateProfileManager.getReadyToAnimate())
                                 && privateProfileManager.getCurrentState() == STATE_ENABLED
                                 ? 0 : 1);
+                        Log.d(TAG, "onBindViewHolder: "
+                                + "isPrivateSpaceItem: " + isPrivateSpaceItem
+                        + " isStateTransitioning: " + privateProfileManager.isStateTransitioning()
+                        + " isScrolling: " + privateProfileManager.isScrolling()
+                        + " readyToAnimate: " + privateProfileManager.getReadyToAnimate()
+                        + " currentState: " + privateProfileManager.getCurrentState()
+                        + " currentAlpha: " + icon.getAlpha());
                     }
                     // Views can still be bounded before the app list is updated hence showing icons
                     // after collapsing.
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index fbe9e33..bebef70 100644
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
@@ -26,7 +26,7 @@
 public class SpringLoadedDragController implements OnAlarmListener {
     // how long the user must hover over a mini-screen before it unshrinks
     private static final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
-    private static final long ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST = 2000;
+    private static final long ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST = 3000;
     private static final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
 
     Alarm mAlarm;
diff --git a/src/com/android/launcher3/model/data/TaskItemInfo.kt b/src/com/android/launcher3/model/data/TaskItemInfo.kt
new file mode 100644
index 0000000..fc1cd4d
--- /dev/null
+++ b/src/com/android/launcher3/model/data/TaskItemInfo.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model.data
+
+/**
+ * Temporary class holding a Task ID to allow us to reference a Task when clicking a hotseat item.
+ *
+ * TODO(b/315344726): Remove this class when we have proper Taskbar support for multi-instance apps
+ */
+class TaskItemInfo(val taskId: Int, itemInfo: WorkspaceItemInfo) : WorkspaceItemInfo(itemInfo)