Merge changes from topic "taskbar-running-minimized" into main

* changes:
  Maintain Running Tasks order in Desktop mode
  Change running apps section to use GroupTask instead of AppInfo
This commit is contained in:
Tony Wickham
2024-06-21 21:00:01 +00:00
committed by Android (Google) Code Review
7 changed files with 456 additions and 291 deletions
@@ -16,24 +16,34 @@
package com.android.launcher3.taskbar
import android.app.ActivityManager.RunningTaskInfo
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)
@@ -45,173 +55,292 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
@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.isEnabled = true
recentAppsController.setApps(
ALL_APP_PACKAGES.map { createTestAppInfo(packageName = it) }.toTypedArray()
)
recentAppsController.canShowRunningApps = true
val listenerCaptor = ArgumentCaptor.forClass(RecentTasksChangedListener::class.java)
verify(mockRecentsModel).registerRecentTasksChangedListener(listenerCaptor.capture())
recentTasksChangedListener = listenerCaptor.value
}
@Test
fun updateHotseatItemInfos_notInDesktopMode_returnsExistingHotseatItems() {
setInDesktopMode(false)
val hotseatItems =
createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2))
assertThat(recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray()))
.isEqualTo(hotseatItems.toTypedArray())
}
@Test
fun updateHotseatItemInfos_notInDesktopMode_runningApps_returnsExistingHotseatItems() {
setInDesktopMode(false)
val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
val hotseatItems = createHotseatItemsFromPackageNames(hotseatPackages)
val runningTasks =
createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
recentAppsController.updateRunningApps()
fun updateHotseatItemInfos_cantShowRunning_inDesktopMode_returnsAllHotseatItems() {
recentAppsController.canShowRunningApps = false
setInDesktopMode(true)
val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1)
val newHotseatItems =
recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = hotseatPackages,
runningTaskPackages = emptyList(),
recentTaskPackages = emptyList()
)
assertThat(newHotseatItems.map { it?.targetPackage })
.containsExactlyElementsIn(hotseatPackages)
}
@Test
fun updateHotseatItemInfos_noRunningApps_returnsExistingHotseatItems() {
fun updateHotseatItemInfos_canShowRunning_inDesktopMode_returnsNonPredictedHotseatItems() {
recentAppsController.canShowRunningApps = true
setInDesktopMode(true)
val hotseatItems =
createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2))
assertThat(recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray()))
.isEqualTo(hotseatItems.toTypedArray())
}
@Test
fun updateHotseatItemInfos_returnsExistingHotseatItemsAndRunningApps() {
setInDesktopMode(true)
val hotseatItems =
createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2))
val runningTasks =
createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
recentAppsController.updateRunningApps()
val newHotseatItems =
recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
val expectedPackages =
listOf(
HOTSEAT_PACKAGE_1,
HOTSEAT_PACKAGE_2,
RUNNING_APP_PACKAGE_1,
RUNNING_APP_PACKAGE_2,
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 updateHotseatItemInfos_runningAppIsHotseatItem_returnsDistinctItems() {
fun onRecentTasksChanged_cantShowRunning_inDesktopMode_shownTasks_returnsEmptyList() {
recentAppsController.canShowRunningApps = false
setInDesktopMode(true)
val hotseatItems =
createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2))
val runningTasks =
createDesktopTasksFromPackageNames(
listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
)
whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
recentAppsController.updateRunningApps()
val newHotseatItems =
recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
val expectedPackages =
listOf(
HOTSEAT_PACKAGE_1,
HOTSEAT_PACKAGE_2,
RUNNING_APP_PACKAGE_1,
RUNNING_APP_PACKAGE_2,
)
assertThat(newHotseatItems.map { it?.targetPackage })
.containsExactlyElementsIn(expectedPackages)
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 getRunningApps_notInDesktopMode_returnsEmptySet() {
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)
val runningTasks =
createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
recentAppsController.updateRunningApps()
assertThat(recentAppsController.runningApps).isEmpty()
assertThat(recentAppsController.minimizedApps).isEmpty()
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 getRunningApps_inDesktopMode_returnsRunningApps() {
fun onRecentTasksChanged_inDesktopMode_getRunningApps_returnsAllDesktopTasks() {
setInDesktopMode(true)
val runningTasks =
createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
recentAppsController.updateRunningApps()
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()
}
assertThat(recentAppsController.runningApps)
.containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
assertThat(recentAppsController.minimizedApps).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 runningTasks =
ArrayList(
listOf(
createDesktopTaskInfo(RUNNING_APP_PACKAGE_1) { isVisible = true },
createDesktopTaskInfo(RUNNING_APP_PACKAGE_2) { isVisible = true },
createDesktopTaskInfo(RUNNING_APP_PACKAGE_3) { isVisible = false },
)
)
whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
recentAppsController.updateRunningApps()
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)
}
assertThat(recentAppsController.runningApps)
.containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3)
assertThat(recentAppsController.minimizedApps).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) }
}
private fun createDesktopTasksFromPackageNames(
packageNames: List<String>
): ArrayList<RunningTaskInfo> {
return ArrayList(packageNames.map { createDesktopTaskInfo(packageName = it) })
}
private fun createDesktopTaskInfo(
packageName: String,
init: RunningTaskInfo.() -> Unit = { isVisible = true },
): RunningTaskInfo {
return RunningTaskInfo().apply {
taskId = nextTaskId++
configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
realActivity = ComponentName(packageName, "TestActivity")
init()
return packageNames.map {
createTestAppInfo(packageName = it).apply {
container =
if (it.startsWith("predicted")) {
CONTAINER_HOTSEAT_PREDICTION
} else {
CONTAINER_HOTSEAT
}
}
}
}
@@ -220,23 +349,54 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
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"
val ALL_APP_PACKAGES =
listOf(
HOTSEAT_PACKAGE_1,
HOTSEAT_PACKAGE_2,
RUNNING_APP_PACKAGE_1,
RUNNING_APP_PACKAGE_2,
RUNNING_APP_PACKAGE_3,
)
const val RECENT_PACKAGE_1 = "recent1"
const val RECENT_PACKAGE_2 = "recent2"
}
}