diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 060173a2d7..e09181e4e6 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -85,6 +85,7 @@ import androidx.core.view.WindowInsetsCompat; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Flags; import com.android.launcher3.LauncherPrefs; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; @@ -1328,7 +1329,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext { mControllers.uiController.onTaskbarIconLaunched(api); mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true); } - } else if (tag instanceof TaskItemInfo info) { + } else if (tag instanceof TaskItemInfo info && !Flags.enableMultiInstanceMenuTaskbar()) { RemoteTransition remoteTransition = canUnminimizeDesktopTask(info.getTaskId()) ? createUnminimizeRemoteTransition() : null; diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt index a059b22888..e654fe84c2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt @@ -19,6 +19,7 @@ import android.content.Context import android.window.DesktopModeFlags import androidx.annotation.VisibleForTesting import com.android.launcher3.BubbleTextView.RunningAppState +import com.android.launcher3.Flags import com.android.launcher3.Flags.enableRecentsInTaskbar import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.model.data.TaskItemInfo @@ -276,27 +277,60 @@ class TaskbarRecentAppsController(context: Context, private val recentsModel: Re return newOrder } + /** + * Computes the list of running tasks to be shown in the recent apps section of the taskbar in + * desktop mode, taking into account deduplication against hotseat items and existing tasks. + */ private fun computeShownRunningTasks(): List { if (!canShowRunningApps) { return emptyList() } - 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 desktopTasks = getOrderedAndWrappedDesktopTasks() + val newShownTasks = - shownTasks - .filter { !it.supportsMultipleTasks() } - .filter { it.task1.key.id in desktopTaskIds } - .toMutableList() - // Add any new Tasks, maintaining the order from previous shownTasks. - 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 } + if (Flags.enableMultiInstanceMenuTaskbar()) { + val deduplicatedDesktopTasks = + desktopTasks.distinctBy { Pair(it.task1.key.packageName, it.task1.key.userId) } + + shownTasks + .filter { + !it.supportsMultipleTasks() && + it.task1.key.id in deduplicatedDesktopTasks.map { it.task1.key.id } + } + .toMutableList() + .apply { + addAll( + deduplicatedDesktopTasks.filter { currentTask -> + val currentTaskKey = currentTask.task1.key + currentTaskKey.id !in shownTasks.map { it.task1.key.id } && + shownHotseatItems.none { hotseatItem -> + hotseatItem.targetPackage == currentTaskKey.packageName && + hotseatItem.user.identifier == currentTaskKey.userId + } + } + ) + } + } else { + val desktopTaskIds = desktopTasks.map { it.task1.key.id } + val shownHotseatItemTaskIds = + shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId } + + shownTasks + .filter { !it.supportsMultipleTasks() && it.task1.key.id in desktopTaskIds } + .toMutableList() + .apply { + addAll( + desktopTasks.filter { desktopTask -> + desktopTask.task1.key.id !in + shownTasks.map { shownTask -> shownTask.task1.key.id } + } + ) + removeAll { it.task1.key.id in shownHotseatItemTaskIds } + } + } + + return newShownTasks } private fun computeShownRecentTasks(): List { @@ -305,7 +339,6 @@ class TaskbarRecentAppsController(context: Context, private val recentsModel: Re } // Remove the current task. val allRecentTasks = allRecentTasks.subList(0, allRecentTasks.size - 1) - // TODO(b/315344726 Multi-instance support): dedupe Tasks of the same package too var shownTasks = dedupeHotseatTasks(allRecentTasks, shownHotseatItems) if (shownTasks.size > MAX_RECENT_TASKS) { // Remove any tasks older than MAX_RECENT_TASKS. @@ -318,10 +351,22 @@ class TaskbarRecentAppsController(context: Context, private val recentsModel: Re groupTasks: List, shownHotseatItems: List, ): List { - val hotseatPackages = shownHotseatItems.map { item -> item.targetPackage } - return groupTasks.filter { groupTask -> - groupTask.hasMultipleTasks() || - !hotseatPackages.contains(groupTask.task1.key.packageName) + return if (Flags.enableMultiInstanceMenuTaskbar()) { + groupTasks.filter { groupTask -> + val taskKey = groupTask.task1.key + // Keep tasks that are group tasks or unique package name/user combinations + groupTask.hasMultipleTasks() || + shownHotseatItems.none { + it.targetPackage == taskKey.packageName && + it.user.identifier == taskKey.userId + } + } + } else { + val hotseatPackages = shownHotseatItems.map { it.targetPackage } + groupTasks.filter { groupTask -> + groupTask.hasMultipleTasks() || + !hotseatPackages.contains(groupTask.task1.key.packageName) + } } } 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 36e8a82e1c..3121c56217 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt @@ -19,9 +19,11 @@ package com.android.launcher3.taskbar import android.animation.AnimatorTestRule import android.content.ComponentName import android.content.Intent +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import androidx.test.core.app.ApplicationProvider +import com.android.launcher3.Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW import com.android.launcher3.R import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync @@ -64,6 +66,7 @@ import org.mockito.kotlin.whenever FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_BUBBLE_BAR, ) +@DisableFlags(FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR) class TaskbarOverflowTest { @get:Rule(order = 0) val setFlagsRule = SetFlagsRule() diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt index ed0c928f64..fbfc40b37b 100644 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt +++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt @@ -23,10 +23,12 @@ import android.content.Intent import android.content.res.Resources import android.os.Process import android.os.UserHandle +import android.platform.test.annotations.EnableFlags import android.platform.test.rule.TestWatcher import android.testing.AndroidTestingRunner import com.android.internal.R import com.android.launcher3.BubbleTextView.RunningAppState +import com.android.launcher3.Flags import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION import com.android.launcher3.model.data.AppInfo @@ -57,6 +59,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) +@EnableFlags(Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR) class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { @get:Rule val mockitoRule = MockitoJUnit.rule() @@ -323,8 +326,12 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { assertThat(hotseatItem1.taskId).isEqualTo(1) } + /** + * Tests that in desktop mode, when two tasks have the same package name and one is in the + * hotseat, only the hotseat item represents the app, and no duplicate is shown in recent apps. + */ @Test - fun updateHotseatItemInfos_inDesktopMode_twoRunningTasksSamePackage_hotseatCoversFirstTask() { + fun updateHotseatItemInfos_inDesktopMode_twoRunningTasksSamePackage_onlyHotseatCoversTask() { setInDesktopMode(true) val newHotseatItems = @@ -338,16 +345,16 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { recentTaskPackages = emptyList(), ) - // First task is in Hotseat Items + // The 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 + assertThat(hotseatItem1.targetPackage).isEqualTo(HOTSEAT_PACKAGE_1) + + // The other task of the same package is not in shownTasks val shownTasks = recentAppsController.shownTasks.map { it.task1 } - assertThat(shownTasks) - .containsExactlyElementsIn(listOf(createTask(id = 2, HOTSEAT_PACKAGE_1))) + assertThat(shownTasks).isEmpty() } @Test @@ -530,8 +537,12 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { assertThat(shownTasks).isEqualTo(listOf(task1, task2)) } + /** + * Tests that when multiple instances of the same app are running in desktop mode and the app is + * not in the hotseat, only one instance is shown in the recent apps section. + */ @Test - fun onRecentTasksChanged_inDesktopMode_multiInstance_shownTasks_maintainsOrder() { + fun onRecentTasksChanged_inDesktopMode_multiInstance_noHotseat_shownTasksHasOneInstance() { setInDesktopMode(true) val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1) @@ -541,43 +552,10 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { recentTaskPackages = emptyList(), ) - prepareHotseatAndRunningAndRecentApps( - hotseatPackages = emptyList(), - runningTasks = listOf(task2, task1), - recentTaskPackages = emptyList(), - ) - + // Assert that shownTasks contains only one instance of the app 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)) + assertThat(shownTasks).hasSize(1) + assertThat(shownTasks[0].key.packageName).isEqualTo(RUNNING_APP_PACKAGE_1) } @Test