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
@@ -15,9 +15,6 @@
*/
package com.android.launcher3.taskbar;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
import android.util.SparseArray;
import android.view.View;
@@ -29,7 +26,6 @@ import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -37,8 +33,6 @@ import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.RecentsModel;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -54,7 +48,7 @@ import java.util.function.Predicate;
* Launcher model Callbacks for rendering taskbar.
*/
public class TaskbarModelCallbacks implements
BgDataModel.Callbacks, LauncherBindableItemsContainer, RecentsModel.RunningTasksListener {
BgDataModel.Callbacks, LauncherBindableItemsContainer {
private final SparseArray<ItemInfo> mHotseatItems = new SparseArray<>();
private List<ItemInfo> mPredictedItems = Collections.emptyList();
@@ -68,8 +62,6 @@ public class TaskbarModelCallbacks implements
// Used to defer any UI updates during the SUW unstash animation.
private boolean mDeferUpdatesForSUW;
private Runnable mDeferredUpdates;
private final DesktopVisibilityController.DesktopVisibilityListener mDesktopVisibilityListener =
visible -> updateRunningApps();
public TaskbarModelCallbacks(
TaskbarActivityContext context, TaskbarView container) {
@@ -79,39 +71,6 @@ public class TaskbarModelCallbacks implements
public void init(TaskbarControllers controllers) {
mControllers = controllers;
if (mControllers.taskbarRecentAppsController.getCanShowRunningApps()) {
RecentsModel.INSTANCE.get(mContext).registerRunningTasksListener(this);
if (shouldShowRunningAppsInDesktopMode()) {
DesktopVisibilityController desktopVisibilityController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
desktopVisibilityController.registerDesktopVisibilityListener(
mDesktopVisibilityListener);
}
}
}
}
/**
* Unregisters listeners in this class.
*/
public void unregisterListeners() {
RecentsModel.INSTANCE.get(mContext).unregisterRunningTasksListener();
if (shouldShowRunningAppsInDesktopMode()) {
DesktopVisibilityController desktopVisibilityController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
desktopVisibilityController.unregisterDesktopVisibilityListener(
mDesktopVisibilityListener);
}
}
}
private boolean shouldShowRunningAppsInDesktopMode() {
// TODO(b/335401172): unify DesktopMode checks in Launcher
return enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps();
}
@Override
@@ -232,10 +191,12 @@ public class TaskbarModelCallbacks implements
predictionNextIndex++;
}
}
hotseatItemInfos = mControllers.taskbarRecentAppsController
.updateHotseatItemInfos(hotseatItemInfos);
Set<String> runningPackages = mControllers.taskbarRecentAppsController.getRunningApps();
Set<String> minimizedPackages = mControllers.taskbarRecentAppsController.getMinimizedApps();
final TaskbarRecentAppsController recentAppsController =
mControllers.taskbarRecentAppsController;
hotseatItemInfos = recentAppsController.updateHotseatItemInfos(hotseatItemInfos);
Set<String> runningPackages = recentAppsController.getRunningAppPackages();
Set<String> minimizedPackages = recentAppsController.getMinimizedAppPackages();
if (mDeferUpdatesForSUW) {
ItemInfo[] finalHotseatItemInfos = hotseatItemInfos;
@@ -270,21 +231,11 @@ public class TaskbarModelCallbacks implements
}
}
@Override
public void onRunningTasksChanged() {
updateRunningApps();
}
/** Called when there's a change in running apps to update the UI. */
public void commitRunningAppsToUI() {
commitItemsToUI();
}
/** Call TaskbarRecentAppsController to update running apps with mHotseatItems. */
public void updateRunningApps() {
mControllers.taskbarRecentAppsController.updateRunningApps();
}
@Override
public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
mControllers.taskbarPopupController.setDeepShortcutMap(deepShortcutMapCopy);
@@ -296,7 +247,6 @@ public class TaskbarModelCallbacks implements
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
Preconditions.assertUIThread();
mControllers.taskbarAllAppsController.setApps(apps, flags, packageUserKeytoUidMap);
mControllers.taskbarRecentAppsController.setApps(apps);
}
protected void dumpLogs(String prefix, PrintWriter pw) {
@@ -15,16 +15,13 @@
*/
package com.android.launcher3.taskbar
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration
import androidx.annotation.VisibleForTesting
import com.android.launcher3.Flags.enableRecentsInTaskbar
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.statehandlers.DesktopVisibilityController
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.quickstep.RecentsModel
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
import com.android.window.flags.Flags.enableDesktopWindowingMode
import com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps
import java.io.PrintWriter
@@ -42,22 +39,22 @@ class TaskbarRecentAppsController(
) : LoggableTaskbarController {
// TODO(b/335401172): unify DesktopMode checks in Launcher.
val canShowRunningApps =
var canShowRunningApps =
enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps()
// TODO(b/343532825): Add a setting to disable Recents even when the flag is on.
var isEnabled: Boolean = enableRecentsInTaskbar() || canShowRunningApps
@VisibleForTesting
set(isEnabledFromTest){
set(isEnabledFromTest) {
field = isEnabledFromTest
}
// Initialized in init.
private lateinit var controllers: TaskbarControllers
private var apps: Array<AppInfo>? = null
private var allRunningDesktopAppInfos: List<AppInfo>? = null
private var allMinimizedDesktopAppInfos: List<AppInfo>? = null
private var shownHotseatItems: List<ItemInfo> = emptyList()
private var allRecentTasks: List<GroupTask> = emptyList()
private var desktopTask: DesktopTask? = null
// TODO(next CL): actually read and show these
var shownTasks: List<GroupTask> = emptyList()
private set
private val desktopVisibilityController: DesktopVisibilityController?
get() = desktopVisibilityControllerProvider()
@@ -65,122 +62,139 @@ class TaskbarRecentAppsController(
private val isInDesktopMode: Boolean
get() = desktopVisibilityController?.areDesktopTasksVisible() ?: false
val runningApps: Set<String>
val runningAppPackages: Set<String>
/**
* Returns the package names 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() {
if (!isEnabled || !isInDesktopMode) {
if (!canShowRunningApps || !isInDesktopMode) {
return emptySet()
}
return allRunningDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet()
val tasks = desktopTask?.tasks ?: return emptySet()
return tasks.map { task -> task.key.packageName }.toSet()
}
val minimizedApps: Set<String>
val minimizedAppPackages: Set<String>
/**
* 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).
*/
get() {
if (!isInDesktopMode) {
if (!canShowRunningApps || !isInDesktopMode) {
return emptySet()
}
return allMinimizedDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet()
?: emptySet()
val desktopTasks = desktopTask?.tasks ?: return emptySet()
val packageToTasks = desktopTasks.groupBy { it.key.packageName }
return packageToTasks.filterValues { tasks -> tasks.all { !it.isVisible } }.keys
}
private val recentTasksChangedListener =
RecentsModel.RecentTasksChangedListener { reloadRecentTasksIfNeeded() }
// TODO(b/343291428): add TaskVisualsChangListener as well (for calendar/clock?)
// Used to keep track of the last requested task list ID, so that we do not request to load the
// tasks again if we have already requested it and the task list has not changed
private var taskListChangeId = -1
fun init(taskbarControllers: TaskbarControllers) {
controllers = taskbarControllers
recentsModel.registerRecentTasksChangedListener(recentTasksChangedListener)
reloadRecentTasksIfNeeded()
}
fun onDestroy() {
apps = null
}
/** Stores the current [AppInfo] instances, no-op except in desktop environment. */
fun setApps(apps: Array<AppInfo>?) {
this.apps = apps
recentsModel.unregisterRecentTasksChangedListener()
}
/** Called to update hotseatItems, in order to de-dupe them from Recent/Running tasks later. */
// TODO(next CL): add new section of Tasks instead of changing Hotseat items
fun updateHotseatItemInfos(hotseatItems: Array<ItemInfo?>): Array<ItemInfo?> {
if (!isEnabled || !isInDesktopMode) {
// Ignore predicted apps - we show running or recent apps instead.
val removePredictions = isInDesktopMode && canShowRunningApps
if (!removePredictions) {
shownHotseatItems = hotseatItems.filterNotNull()
onRecentsOrHotseatChanged()
return hotseatItems
}
val newHotseatItemInfos =
shownHotseatItems =
hotseatItems
.filterNotNull()
// Ignore predicted apps - we show running apps instead
.filter { itemInfo -> !itemInfo.isPredictedItem }
.toMutableList()
val runningDesktopAppInfos =
allRunningDesktopAppInfos?.let {
getRunningDesktopAppInfosExceptHotseatApps(it, newHotseatItemInfos.toList())
onRecentsOrHotseatChanged()
return shownHotseatItems.toTypedArray()
}
private fun reloadRecentTasksIfNeeded() {
if (!recentsModel.isTaskListValid(taskListChangeId)) {
taskListChangeId =
recentsModel.getTasks { tasks ->
allRecentTasks = tasks
desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
onRecentsOrHotseatChanged()
controllers.taskbarViewController.commitRunningAppsToUI()
}
}
}
private fun onRecentsOrHotseatChanged() {
shownTasks =
if (isInDesktopMode) {
computeShownRunningTasks()
} else {
computeShownRecentTasks()
}
if (runningDesktopAppInfos != null) {
newHotseatItemInfos.addAll(runningDesktopAppInfos)
}
return newHotseatItemInfos.toTypedArray()
}
private fun getRunningDesktopAppInfosExceptHotseatApps(
allRunningDesktopAppInfos: List<AppInfo>,
hotseatItems: List<ItemInfo>
): List<ItemInfo> {
val hotseatPackages = hotseatItems.map { it.targetPackage }
return allRunningDesktopAppInfos
.filter { appInfo -> !hotseatPackages.contains(appInfo.targetPackage) }
.map { WorkspaceItemInfo(it) }
}
private fun getDesktopRunningTasks(): List<RunningTaskInfo> =
recentsModel.runningTasks.filter { taskInfo: RunningTaskInfo ->
taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
}
// TODO(b/335398876) fetch app icons from Tasks instead of AppInfos
private fun getAppInfosFromRunningTasks(tasks: List<RunningTaskInfo>): List<AppInfo> {
// Early return if apps is empty, since we then have no AppInfo to compare to
if (apps == null) {
private fun computeShownRunningTasks(): List<GroupTask> {
if (!canShowRunningApps) {
return emptyList()
}
val packageNames = tasks.map { it.realActivity?.packageName }.distinct().filterNotNull()
return packageNames
.map { packageName -> apps?.find { app -> packageName == app.targetPackage } }
.filterNotNull()
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 }
// Add any new Tasks, maintaining the order from previous shownTasks.
newShownTasks.addAll(desktopTaskAsList.filter { it.packageNames !in newShownPackages })
return newShownTasks.toList()
}
/** Called to update the list of currently running apps, no-op except in desktop environment. */
fun updateRunningApps() {
if (!isEnabled || !isInDesktopMode) {
return controllers.taskbarViewController.commitRunningAppsToUI()
private fun computeShownRecentTasks(): List<GroupTask> {
// TODO(next CL): implement Recents section
return emptyList()
}
private fun dedupeHotseatTasks(
groupTasks: List<GroupTask>,
shownHotseatItems: List<ItemInfo>
): List<GroupTask> {
val hotseatPackages = shownHotseatItems.map { item -> item.targetPackage }
return groupTasks.filter { groupTask ->
groupTask.hasMultipleTasks() ||
!hotseatPackages.contains(groupTask.task1.key.packageName)
}
val runningTasks = getDesktopRunningTasks()
val runningAppInfo = getAppInfosFromRunningTasks(runningTasks)
allRunningDesktopAppInfos = runningAppInfo
updateMinimizedApps(runningTasks, runningAppInfo)
controllers.taskbarViewController.commitRunningAppsToUI()
}
private fun updateMinimizedApps(
runningTasks: List<RunningTaskInfo>,
runningAppInfo: List<AppInfo>,
) {
val allRunningAppTasks =
runningAppInfo
.mapNotNull { appInfo -> appInfo.targetPackage?.let { appInfo to it } }
.associate { (appInfo, targetPackage) ->
appInfo to
runningTasks
.filter { it.realActivity?.packageName == targetPackage }
.map { it.taskId }
}
val minimizedTaskIds = runningTasks.associate { it.taskId to !it.isVisible }
allMinimizedDesktopAppInfos =
allRunningAppTasks
.filterValues { taskIds -> taskIds.all { minimizedTaskIds[it] ?: false } }
.keys
.toList()
}
override fun dumpLogs(prefix: String, pw: PrintWriter) {
pw.println("$prefix TaskbarRecentAppsController:")
pw.println("$prefix\tisEnabled=$isEnabled")
pw.println("$prefix\tcanShowRunningApps=$canShowRunningApps")
// TODO(next CL): add more logs
pw.println("$prefix\tshownHotseatItems=${shownHotseatItems.map{item->item.targetPackage}}")
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")
}
private val GroupTask.packageNames: List<String>
get() = tasks.map { task -> task.key.packageName }
}
@@ -224,7 +224,6 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
}
LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
mModelCallbacks.unregisterListeners();
}
public boolean areIconsVisible() {
@@ -869,6 +868,11 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
return mTaskbarView.isEventOverAnyItem(ev);
}
/** Called when there's a change in running apps to update the UI. */
public void commitRunningAppsToUI() {
mModelCallbacks.commitRunningAppsToUI();
}
@Override
public void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "TaskbarViewController:");
@@ -889,14 +893,4 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
mModelCallbacks.dumpLogs(prefix + "\t", pw);
}
/** Called when there's a change in running apps to update the UI. */
public void commitRunningAppsToUI() {
mModelCallbacks.commitRunningAppsToUI();
}
/** Call TaskbarModelCallbacks to update running apps. */
public void updateRunningApps() {
mModelCallbacks.updateRunningApps();
}
}
@@ -33,7 +33,7 @@ class TaskbarFeatureEvaluator(
val hasNavButtons = taskbarActivityContext.isThreeButtonNav
val hasRecents: Boolean
get() = taskbarControllers.taskbarRecentAppsController.isEnabled
get() = taskbarControllers.taskbarRecentAppsController.shownTasks.isNotEmpty()
val hasDivider: Boolean
get() = enableTaskbarPinning() || hasRecents
@@ -70,7 +70,8 @@ public class RecentTasksList {
private TaskLoadResult mResultsBg = INVALID_RESULT;
private TaskLoadResult mResultsUi = INVALID_RESULT;
private RecentsModel.RunningTasksListener mRunningTasksListener;
private @Nullable RecentsModel.RunningTasksListener mRunningTasksListener;
private @Nullable RecentsModel.RecentTasksChangedListener mRecentTasksChangedListener;
// Tasks are stored in order of least recently launched to most recently launched.
private ArrayList<ActivityManager.RunningTaskInfo> mRunningTasks;
@@ -199,6 +200,9 @@ public class RecentTasksList {
public void onRecentTasksChanged() {
invalidateLoadedTasks();
if (mRecentTasksChangedListener != null) {
mRecentTasksChangedListener.onRecentTasksChanged();
}
}
private synchronized void invalidateLoadedTasks() {
@@ -221,6 +225,21 @@ public class RecentTasksList {
mRunningTasksListener = null;
}
/**
* Registers a listener for running tasks
*/
public void registerRecentTasksChangedListener(
RecentsModel.RecentTasksChangedListener listener) {
mRecentTasksChangedListener = listener;
}
/**
* Removes the previously registered running tasks listener
*/
public void unregisterRecentTasksChangedListener() {
mRecentTasksChangedListener = null;
}
private void initRunningTasks(ArrayList<ActivityManager.RunningTaskInfo> runningTasks) {
// Tasks are retrieved in order of most recently launched/used to least recently launched.
mRunningTasks = new ArrayList<>(runningTasks);
@@ -357,6 +376,7 @@ public class RecentTasksList {
task.setLastSnapshotData(taskInfo);
task.positionInParent = taskInfo.positionInParent;
task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
task.isVisible = taskInfo.isVisible;
tasks.add(task);
}
return new DesktopTask(tasks);
@@ -42,6 +42,7 @@ import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.quickstep.recents.data.RecentTasksDataSource;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TaskVisualsChangeListener;
import com.android.systemui.shared.recents.model.Task;
@@ -301,6 +302,8 @@ public class RecentsModel implements RecentTasksDataSource, IconChangeListener,
/**
* Registers a listener for running tasks
* TODO(b/343292503): Should we remove RunningTasksListener entirely if it's not needed?
* (Note that Desktop mode gets the running tasks by checking {@link DesktopTask#tasks}
*/
public void registerRunningTasksListener(RunningTasksListener listener) {
mTaskList.registerRunningTasksListener(listener);
@@ -313,6 +316,20 @@ public class RecentsModel implements RecentTasksDataSource, IconChangeListener,
mTaskList.unregisterRunningTasksListener();
}
/**
* Registers a listener for recent tasks
*/
public void registerRecentTasksChangedListener(RecentTasksChangedListener listener) {
mTaskList.registerRecentTasksChangedListener(listener);
}
/**
* Removes the previously registered running tasks listener
*/
public void unregisterRecentTasksChangedListener() {
mTaskList.unregisterRecentTasksChangedListener();
}
/**
* Gets the set of running tasks.
*/
@@ -379,4 +396,14 @@ public class RecentsModel implements RecentTasksDataSource, IconChangeListener,
*/
void onRunningTasksChanged();
}
/**
* Listener for receiving recent tasks changes
*/
public interface RecentTasksChangedListener {
/**
* Called when there's a change to recent tasks
*/
void onRecentTasksChanged();
}
}
@@ -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"
}
}