/* * 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.taskbar import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.ComponentName import android.content.Intent import android.os.Process import android.os.UserHandle import android.testing.AndroidTestingRunner import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT 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 import com.android.quickstep.TaskIconCache import com.android.quickstep.util.DesktopTask import com.android.quickstep.util.GroupTask import com.android.systemui.shared.recents.model.Task import com.google.common.truth.Truth.assertThat import java.util.function.Consumer import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.junit.MockitoJUnit import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { @get:Rule val mockitoRule = MockitoJUnit.rule() @Mock private lateinit var mockIconCache: TaskIconCache @Mock private lateinit var mockRecentsModel: RecentsModel @Mock private lateinit var mockDesktopVisibilityController: DesktopVisibilityController private var taskListChangeId: Int = 1 private lateinit var recentAppsController: TaskbarRecentAppsController private lateinit var recentTasksChangedListener: RecentTasksChangedListener private lateinit var userHandle: UserHandle @Before fun setUp() { super.setup() userHandle = Process.myUserHandle() whenever(mockRecentsModel.iconCache).thenReturn(mockIconCache) recentAppsController = TaskbarRecentAppsController(mockRecentsModel) { mockDesktopVisibilityController } recentAppsController.init(taskbarControllers) recentAppsController.canShowRunningApps = true recentAppsController.canShowRecentApps = true 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 fun updateHotseatItemInfos_cantShowRunning_inDesktopMode_returnsAllHotseatItems() { recentAppsController.canShowRunningApps = false setInDesktopMode(true) val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1) val newHotseatItems = prepareHotseatAndRunningAndRecentApps( hotseatPackages = hotseatPackages, runningTasks = emptyList(), recentTaskPackages = emptyList() ) assertThat(newHotseatItems.map { it?.targetPackage }) .containsExactlyElementsIn(hotseatPackages) } @Test fun updateHotseatItemInfos_cantShowRecent_notInDesktopMode_returnsAllHotseatItems() { recentAppsController.canShowRecentApps = false setInDesktopMode(false) val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1) val newHotseatItems = prepareHotseatAndRunningAndRecentApps( hotseatPackages = hotseatPackages, runningTasks = emptyList(), recentTaskPackages = emptyList() ) assertThat(newHotseatItems.map { it?.targetPackage }) .containsExactlyElementsIn(hotseatPackages) } @Test fun updateHotseatItemInfos_canShowRunning_inDesktopMode_returnsNonPredictedHotseatItems() { recentAppsController.canShowRunningApps = true setInDesktopMode(true) val newHotseatItems = prepareHotseatAndRunningAndRecentApps( hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1), runningTasks = emptyList(), recentTaskPackages = emptyList() ) val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2) assertThat(newHotseatItems.map { it?.targetPackage }) .containsExactlyElementsIn(expectedPackages) } @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), runningTasks = emptyList(), recentTaskPackages = emptyList() ) val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2) assertThat(newHotseatItems.map { it?.targetPackage }) .containsExactlyElementsIn(expectedPackages) } @Test fun onRecentTasksChanged_cantShowRunning_inDesktopMode_shownTasks_returnsEmptyList() { recentAppsController.canShowRunningApps = false setInDesktopMode(true) prepareHotseatAndRunningAndRecentApps( hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1), runningTasks = listOf( createTask(id = 1, RUNNING_APP_PACKAGE_1), createTask(id = 2, RUNNING_APP_PACKAGE_2) ), recentTaskPackages = emptyList() ) assertThat(recentAppsController.shownTasks).isEmpty() } @Test fun onRecentTasksChanged_cantShowRecent_notInDesktopMode_shownTasks_returnsEmptyList() { recentAppsController.canShowRecentApps = false setInDesktopMode(false) prepareHotseatAndRunningAndRecentApps( hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1), runningTasks = emptyList(), recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2) ) assertThat(recentAppsController.shownTasks).isEmpty() } @Test fun onRecentTasksChanged_notInDesktopMode_noRecentTasks_shownTasks_returnsEmptyList() { setInDesktopMode(false) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = listOf( createTask(id = 1, RUNNING_APP_PACKAGE_1), createTask(id = 2, RUNNING_APP_PACKAGE_2) ), recentTaskPackages = emptyList() ) assertThat(recentAppsController.shownTasks).isEmpty() assertThat(recentAppsController.minimizedTaskIds).isEmpty() } @Test fun onRecentTasksChanged_inDesktopMode_noRunningApps_shownTasks_returnsEmptyList() { setInDesktopMode(true) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = emptyList(), recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2) ) assertThat(recentAppsController.shownTasks).isEmpty() } @Test fun onRecentTasksChanged_inDesktopMode_shownTasks_returnsRunningTasks() { setInDesktopMode(true) val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = listOf(task1, task2), recentTaskPackages = emptyList() ) 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(), runningTasks = listOf(task1, task2), recentTaskPackages = emptyList() ) assertThat(recentAppsController.runningTaskIds).isEmpty() assertThat(recentAppsController.minimizedTaskIds).isEmpty() } @Test fun onRecentTasksChanged_inDesktopMode_getRunningApps_returnsAllDesktopTasks() { setInDesktopMode(true) val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = listOf(task1, task2), recentTaskPackages = emptyList() ) assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2)) assertThat(recentAppsController.minimizedTaskIds).isEmpty() } @Test fun onRecentTasksChanged_inDesktopMode_getRunningApps_includesHotseat() { setInDesktopMode(true) 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), runningTasks = runningTasks, recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2) ) assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2, 3)) assertThat(recentAppsController.minimizedTaskIds).isEmpty() } @Test fun onRecentTasksChanged_inDesktopMode_allAppsRunningAndInvisibleAppsMinimized() { setInDesktopMode(true) 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(), runningTasks = runningTasks, recentTaskPackages = emptyList() ) assertThat(recentAppsController.runningTaskIds).containsExactly(1, 2, 3) assertThat(recentAppsController.minimizedTaskIds).containsExactly(3) } @Test fun onRecentTasksChanged_inDesktopMode_samePackage_differentTasks_severalRunningTasks() { setInDesktopMode(true) val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = listOf(task1, task2), recentTaskPackages = emptyList() ) assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2)) } @Test fun onRecentTasksChanged_inDesktopMode_shownTasks_maintainsOrder() { setInDesktopMode(true) val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1) val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2) 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 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 fun onRecentTasksChanged_notInDesktopMode_shownTasks_maintainsRecency() { setInDesktopMode(false) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = emptyList(), recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3) ) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = emptyList(), recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1) ) val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } // Most recent packages, minus the currently running one (RECENT_PACKAGE_1). assertThat(shownPackages).isEqualTo(listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3)) } @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(), runningTasks = listOf(task1, task2), recentTaskPackages = emptyList() ) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = listOf(task2, task1, task3), recentTaskPackages = emptyList() ) val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } val expectedOrder = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3) assertThat(shownPackages).isEqualTo(expectedOrder) } @Test fun onRecentTasksChanged_notInDesktopMode_addTask_shownTasks_maintainsRecency() { setInDesktopMode(false) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = emptyList(), recentTaskPackages = listOf(RECENT_PACKAGE_3, RECENT_PACKAGE_2) ) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = emptyList(), recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1) ) val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } // Most recent packages, minus the currently running one (RECENT_PACKAGE_1). assertThat(shownPackages).isEqualTo(listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3)) } @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(), runningTasks = listOf(task1, task2, task3), recentTaskPackages = emptyList() ) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = listOf(task2, task1), recentTaskPackages = emptyList() ) val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } assertThat(shownPackages).isEqualTo(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)) } @Test fun onRecentTasksChanged_notInDesktopMode_removeTask_shownTasks_maintainsRecency() { setInDesktopMode(false) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = emptyList(), recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3) ) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = emptyList(), recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3) ) val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } // Most recent packages, minus the currently running one (RECENT_PACKAGE_3). assertThat(shownPackages).isEqualTo(listOf(RECENT_PACKAGE_2)) } @Test fun onRecentTasksChanged_enterDesktopMode_shownTasks_onlyIncludesRunningTasks() { setInDesktopMode(false) 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(), runningTasks = listOf(runningTask1, runningTask2), recentTaskPackages = recentTaskPackages ) setInDesktopMode(true) recentTasksChangedListener.onRecentTasksChanged() val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } assertThat(shownPackages).containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2) } @Test fun onRecentTasksChanged_exitDesktopMode_shownTasks_onlyIncludesRecentTasks() { setInDesktopMode(true) 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(), runningTasks = listOf(runningTask1, runningTask2), recentTaskPackages = recentTaskPackages ) setInDesktopMode(false) recentTasksChangedListener.onRecentTasksChanged() val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } // Don't expect RECENT_PACKAGE_3 because it is currently running. val expectedPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2) assertThat(shownPackages).containsExactlyElementsIn(expectedPackages) } @Test fun onRecentTasksChanged_notInDesktopMode_hasRecentTasks_shownTasks_returnsRecentTasks() { setInDesktopMode(false) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = emptyList(), recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3) ) val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames } // RECENT_PACKAGE_3 is the top task (visible to user) so should be excluded. val expectedPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2) assertThat(shownPackages).containsExactlyElementsIn(expectedPackages) } @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(), runningTasks = listOf(runningTask1, runningTask2), recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2) ) val shownPackages = recentAppsController.shownTasks.map { it.packageNames } // Only 2 recent tasks shown: Desktop Tile + 1 Recent Task val desktopTilePackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2) val recentTaskPackages = listOf(RECENT_PACKAGE_1) val expectedPackages = listOf(desktopTilePackages, recentTaskPackages) assertThat(shownPackages).containsExactlyElementsIn(expectedPackages) } @Test fun onRecentTasksChanged_notInDesktopMode_hasRecentAndSplitTasks_shownTasks_returnsRecentTaskAndPair() { setInDesktopMode(false) prepareHotseatAndRunningAndRecentApps( hotseatPackages = emptyList(), runningTasks = emptyList(), recentTaskPackages = listOf(RECENT_SPLIT_PACKAGES_1, RECENT_PACKAGE_1, RECENT_PACKAGE_2) ) val shownPackages = recentAppsController.shownTasks.map { it.packageNames } // Only 2 recent tasks shown: Pair + 1 Recent Task val pairPackages = RECENT_SPLIT_PACKAGES_1.split("_") val recentTaskPackages = listOf(RECENT_PACKAGE_1) val expectedPackages = listOf(pairPackages, recentTaskPackages) assertThat(shownPackages).containsExactlyElementsIn(expectedPackages) } @Test fun onRecentTasksChanged_notInDesktopMode_noActualChangeToRecents_commitRunningAppsToUI_notCalled() { setInDesktopMode(false) prepareHotseatAndRunningAndRecentApps( hotseatPackages = 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(), runningTasks = emptyList(), recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2) ) verify(taskbarViewController, times(1)).commitRunningAppsToUI() } @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(), 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(), runningTasks = listOf(runningTask1, runningTask2), recentTaskPackages = emptyList() ) verify(taskbarViewController, times(1)).commitRunningAppsToUI() } @Test fun onRecentTasksChanged_onlyMinimizedChanges_commitRunningAppsToUI_isCalled() { setInDesktopMode(true) 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(), 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(), runningTasks = listOf(task1Minimized, task2Minimized), recentTaskPackages = emptyList() ) verify(taskbarViewController, times(2)).commitRunningAppsToUI() } @Test 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, runningTasks = originalTasks, recentTaskPackages = emptyList() ) verify(taskbarViewController, times(1)).commitRunningAppsToUI() // Call onRecentTasksChanged() again with a new running app, verify we update UI. prepareHotseatAndRunningAndRecentApps( hotseatPackages = hotseatPackages, runningTasks = newTasks, recentTaskPackages = emptyList() ) verify(taskbarViewController, times(2)).commitRunningAppsToUI() } private fun prepareHotseatAndRunningAndRecentApps( hotseatPackages: List, runningTasks: List, recentTaskPackages: List, ): Array { val hotseatItems = createHotseatItemsFromPackageNames(hotseatPackages) recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray()) updateRecentTasks(runningTasks, recentTaskPackages) return recentAppsController.shownHotseatItems.toTypedArray() } private fun updateRecentTasks( runningTasks: List, recentTaskPackages: List, ) { val recentTasks = createRecentTasksFromPackageNames(recentTaskPackages) val allTasks = ArrayList().apply { if (!runningTasks.isEmpty()) { add(DesktopTask(ArrayList(runningTasks))) } addAll(recentTasks) } doAnswer { val callback: Consumer> = it.getArgument(0) callback.accept(allTasks) taskListChangeId } .whenever(mockRecentsModel) .getTasks(any>>()) recentTasksChangedListener.onRecentTasksChanged() } private fun createHotseatItemsFromPackageNames(packageNames: List): List { 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( packageName: String = "testPackageName", className: String = "testClassName" ) = AppInfo(ComponentName(packageName, className), className /* title */, userHandle, Intent()) private fun createRecentTasksFromPackageNames(packageNames: List): List { return packageNames.map { packageName -> if (packageName.startsWith("split")) { val splitPackages = packageName.split("_") GroupTask( createTask(100, splitPackages[0]), createTask(101, splitPackages[1]), /* splitBounds = */ null ) } else { // 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(id: Int, packageName: String, isVisible: Boolean = true): Task { return Task( Task.TaskKey( id, WINDOWING_MODE_FREEFORM, Intent().apply { `package` = packageName }, ComponentName(packageName, "TestActivity"), userHandle.identifier, 0 ) ) .apply { this.isVisible = isVisible } } private fun setInDesktopMode(inDesktopMode: Boolean) { whenever(mockDesktopVisibilityController.areDesktopTasksVisible()).thenReturn(inDesktopMode) } private val GroupTask.packageNames: List get() = tasks.map { task -> task.key.packageName } private companion object { const val HOTSEAT_PACKAGE_1 = "hotseat1" const val HOTSEAT_PACKAGE_2 = "hotseat2" const val PREDICTED_PACKAGE_1 = "predicted1" const val RUNNING_APP_PACKAGE_1 = "running1" const val RUNNING_APP_PACKAGE_2 = "running2" const val RUNNING_APP_PACKAGE_3 = "running3" const val RECENT_PACKAGE_1 = "recent1" const val RECENT_PACKAGE_2 = "recent2" const val RECENT_PACKAGE_3 = "recent3" const val RECENT_SPLIT_PACKAGES_1 = "split1_split2" } }