Files
Lawnchair/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
T
Tony Wickham 7c7c90528d Maintain Running Tasks order in Desktop mode
Fixes: 345034545
Test: TaskbarRecentAppsControllerTest
Flag: com.android.launcher3.enable_recents_in_taskbar
Change-Id: I4ab856c5293625c5279d4713450e5ea2d3b0ed85
2024-06-19 00:27:30 +00:00

403 lines
17 KiB
Kotlin

/*
* 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.statehandlers.DesktopVisibilityController
import com.android.quickstep.RecentsModel
import com.android.quickstep.RecentsModel.RecentTasksChangedListener
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.verify
import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
@get:Rule val mockitoRule = MockitoJUnit.rule()
@Mock private lateinit var mockRecentsModel: RecentsModel
@Mock private lateinit var mockDesktopVisibilityController: DesktopVisibilityController
private var nextTaskId: Int = 500
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()
recentAppsController =
TaskbarRecentAppsController(mockRecentsModel) { mockDesktopVisibilityController }
recentAppsController.init(taskbarControllers)
recentAppsController.canShowRunningApps = true
val listenerCaptor = ArgumentCaptor.forClass(RecentTasksChangedListener::class.java)
verify(mockRecentsModel).registerRecentTasksChangedListener(listenerCaptor.capture())
recentTasksChangedListener = listenerCaptor.value
}
@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,
runningTaskPackages = 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),
runningTaskPackages = 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),
runningTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
recentTaskPackages = emptyList()
)
assertThat(recentAppsController.shownTasks).isEmpty()
}
@Test
fun onRecentTasksChanged_inDesktopMode_noRunningApps_shownTasks_returnsEmptyList() {
setInDesktopMode(true)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
assertThat(recentAppsController.shownTasks).isEmpty()
}
@Test
fun onRecentTasksChanged_inDesktopMode_shownTasks_returnsRunningTasks() {
setInDesktopMode(true)
val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages = runningTaskPackages,
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)
}
@Test
fun onRecentTasksChanged_notInDesktopMode_getRunningApps_returnsEmptySet() {
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
recentTaskPackages = emptyList()
)
assertThat(recentAppsController.runningAppPackages).isEmpty()
assertThat(recentAppsController.minimizedAppPackages).isEmpty()
}
@Test
fun onRecentTasksChanged_inDesktopMode_getRunningApps_returnsAllDesktopTasks() {
setInDesktopMode(true)
val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages = runningTaskPackages,
recentTaskPackages = emptyList()
)
assertThat(recentAppsController.runningAppPackages)
.containsExactlyElementsIn(runningTaskPackages)
assertThat(recentAppsController.minimizedAppPackages).isEmpty()
}
@Test
fun onRecentTasksChanged_inDesktopMode_getRunningApps_includesHotseat() {
setInDesktopMode(true)
val runningTaskPackages =
listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
runningTaskPackages = runningTaskPackages,
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
assertThat(recentAppsController.runningAppPackages)
.containsExactlyElementsIn(runningTaskPackages)
assertThat(recentAppsController.minimizedAppPackages).isEmpty()
}
@Test
fun getMinimizedApps_inDesktopMode_returnsAllAppsRunningAndInvisibleAppsMinimized() {
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
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages = runningTaskPackages,
minimizedTaskIndices = minimizedTaskIndices,
recentTaskPackages = emptyList()
)
assertThat(recentAppsController.runningAppPackages)
.containsExactlyElementsIn(runningTaskPackages)
assertThat(recentAppsController.minimizedAppPackages).containsExactly(RUNNING_APP_PACKAGE_3)
}
@Test
fun getMinimizedApps_inDesktopMode_twoTasksSamePackageOneMinimizedReturnsNotMinimized() {
setInDesktopMode(true)
val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_1)
val minimizedTaskIndices = setOf(1) // The second RUNNING_APP_PACKAGE_1 task.
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages = runningTaskPackages,
minimizedTaskIndices = minimizedTaskIndices,
recentTaskPackages = emptyList()
)
assertThat(recentAppsController.runningAppPackages)
.containsExactlyElementsIn(runningTaskPackages.toSet())
assertThat(recentAppsController.minimizedAppPackages).isEmpty()
}
@Test
fun onRecentTasksChanged_inDesktopMode_shownTasks_maintainsOrder() {
setInDesktopMode(true)
val originalOrder = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages = originalOrder,
recentTaskPackages = emptyList()
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages = listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1),
recentTaskPackages = emptyList()
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
assertThat(shownPackages).isEqualTo(originalOrder)
}
@Test
fun onRecentTasksChanged_inDesktopMode_addTask_shownTasks_maintainsOrder() {
setInDesktopMode(true)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
recentTaskPackages = emptyList()
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages =
listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_3),
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_inDesktopMode_removeTask_shownTasks_maintainsOrder() {
setInDesktopMode(true)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages =
listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3),
recentTaskPackages = emptyList()
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages = listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1),
recentTaskPackages = emptyList()
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
assertThat(shownPackages).isEqualTo(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
}
@Test
fun onRecentTasksChanged_enterDesktopMode_shownTasks_onlyIncludesRunningTasks() {
setInDesktopMode(false)
val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
val recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
runningTaskPackages = runningTaskPackages,
recentTaskPackages = recentTaskPackages
)
setInDesktopMode(true)
recentTasksChangedListener.onRecentTasksChanged()
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
assertThat(shownPackages).containsExactlyElementsIn(runningTaskPackages)
}
private fun prepareHotseatAndRunningAndRecentApps(
hotseatPackages: List<String>,
runningTaskPackages: List<String>,
minimizedTaskIndices: Set<Int> = emptySet(),
recentTaskPackages: List<String>,
): Array<ItemInfo?> {
val hotseatItems = createHotseatItemsFromPackageNames(hotseatPackages)
val newHotseatItems =
recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
val runningTasks = createDesktopTask(runningTaskPackages, minimizedTaskIndices)
val recentTasks = createRecentTasksFromPackageNames(recentTaskPackages)
val allTasks =
ArrayList<GroupTask>().apply {
if (runningTasks != null) {
add(runningTasks)
}
addAll(recentTasks)
}
doAnswer {
val callback: Consumer<ArrayList<GroupTask>> = it.getArgument(0)
callback.accept(allTasks)
taskListChangeId
}
.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
}
}
}
}
private fun createTestAppInfo(
packageName: String = "testPackageName",
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 { GroupTask(createTask(it)) }
}
private fun createTask(packageName: String, isVisible: Boolean = true): Task {
return Task(
Task.TaskKey(
nextTaskId++,
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<String>
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"
}
}