Update desktop mode to identify minimized tasks
When a task is minimised, it becomes invisible but is still running.
WMShell now signals to the launcher when a task's visibility changes.
The task bar takes the visibility into account to know if a running task
is, in fact, minimised.
Test: atest NexusLauncherTests:DesktopTaskbarRunningAppsControllerTest
Flag: com.android.window.flags.enable_desktop_windowing_taskbar_running_apps
Bug: 333872717
Change-Id: Iaff6b1240d354bb3c4de8e4884948acf9bf40112
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 2021a0b..08d36d8 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -358,6 +358,9 @@
<dimen name="taskbar_running_app_indicator_height">4dp</dimen>
<dimen name="taskbar_running_app_indicator_width">14dp</dimen>
<dimen name="taskbar_running_app_indicator_top_margin">2dp</dimen>
+ <dimen name="taskbar_minimized_app_indicator_height">2dp</dimen>
+ <dimen name="taskbar_minimized_app_indicator_width">12dp</dimen>
+ <dimen name="taskbar_minimized_app_indicator_top_margin">2dp</dimen>
<!-- Transient taskbar -->
<dimen name="transient_taskbar_padding">12dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt
index 3649c4e..d4bef28 100644
--- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt
@@ -47,6 +47,7 @@
private var apps: Array<AppInfo>? = null
private var allRunningDesktopAppInfos: List<AppInfo>? = null
+ private var allMinimizedDesktopAppInfos: List<AppInfo>? = null
private val desktopVisibilityController: DesktopVisibilityController?
get() = desktopVisibilityControllerProvider()
@@ -95,6 +96,13 @@
return allRunningDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet()
}
+ override fun getMinimizedApps(): Set<String> {
+ if (!isInDesktopMode) {
+ return emptySet()
+ }
+ return allMinimizedDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet()
+ }
+
@VisibleForTesting
public override fun updateRunningApps() {
if (!isInDesktopMode) {
@@ -102,10 +110,34 @@
mControllers.taskbarViewController.commitRunningAppsToUI()
return
}
- allRunningDesktopAppInfos = getRunningDesktopAppInfos()
+ val runningTasks = getDesktopRunningTasks()
+ val runningAppInfo = getAppInfosFromRunningTasks(runningTasks)
+ allRunningDesktopAppInfos = runningAppInfo
+ updateMinimizedApps(runningTasks, runningAppInfo)
mControllers.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()
+ }
+
private fun getRunningDesktopAppInfosExceptHotseatApps(
allRunningDesktopAppInfos: List<AppInfo>,
hotseatItems: List<ItemInfo>
@@ -116,15 +148,10 @@
.map { WorkspaceItemInfo(it) }
}
- private fun getRunningDesktopAppInfos(): List<AppInfo> {
- return getAppInfosFromRunningTasks(
- recentsModel.runningTasks
- .filter { taskInfo: RunningTaskInfo ->
- taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
- }
- .toList()
- )
- }
+ 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> {
@@ -138,9 +165,10 @@
.filterNotNull()
}
- private fun <E> SparseArray<E>.toList(): List<E> {
- return valueIterator().asSequence().toList()
- }
+ private fun getAppInfosFromRunningTask(task: RunningTaskInfo): AppInfo? =
+ apps?.firstOrNull { it.targetPackage == task.realActivity?.packageName }
+
+ private fun <E> SparseArray<E>.toList(): List<E> = valueIterator().asSequence().toList()
companion object {
private const val TAG = "TabletDesktopTaskbarRunningAppsController"
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 9f24d38..35e1c7b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -68,7 +68,7 @@
// Used to defer any UI updates during the SUW unstash animation.
private boolean mDeferUpdatesForSUW;
private Runnable mDeferredUpdates;
- private DesktopVisibilityController.DesktopVisibilityListener mDesktopVisibilityListener =
+ private final DesktopVisibilityController.DesktopVisibilityListener mDesktopVisibilityListener =
visible -> updateRunningApps();
public TaskbarModelCallbacks(
@@ -235,20 +235,23 @@
hotseatItemInfos = mControllers.taskbarRecentAppsController
.updateHotseatItemInfos(hotseatItemInfos);
Set<String> runningPackages = mControllers.taskbarRecentAppsController.getRunningApps();
+ Set<String> minimizedPackages = mControllers.taskbarRecentAppsController.getMinimizedApps();
if (mDeferUpdatesForSUW) {
ItemInfo[] finalHotseatItemInfos = hotseatItemInfos;
mDeferredUpdates = () ->
- commitHotseatItemUpdates(finalHotseatItemInfos, runningPackages);
+ commitHotseatItemUpdates(finalHotseatItemInfos, runningPackages,
+ minimizedPackages);
} else {
- commitHotseatItemUpdates(hotseatItemInfos, runningPackages);
+ commitHotseatItemUpdates(hotseatItemInfos, runningPackages, minimizedPackages);
}
}
- private void commitHotseatItemUpdates(
- ItemInfo[] hotseatItemInfos, Set<String> runningPackages) {
+ private void commitHotseatItemUpdates(ItemInfo[] hotseatItemInfos, Set<String> runningPackages,
+ Set<String> minimizedPackages) {
mContainer.updateHotseatItems(hotseatItemInfos);
- mControllers.taskbarViewController.updateIconViewsRunningStates(runningPackages);
+ mControllers.taskbarViewController.updateIconViewsRunningStates(runningPackages,
+ minimizedPackages);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java
index a29c74b..606ba5b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java
@@ -69,4 +69,9 @@
public Set<String> getRunningApps() {
return emptySet();
}
+
+ /** Returns the set of apps whose tasks are all minimized. */
+ public Set<String> getMinimizedApps() {
+ return emptySet();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 93814b7..23495ad 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -510,14 +510,30 @@
}
/** Updates which icons are marked as running given the Set of currently running packages. */
- public void updateIconViewsRunningStates(Set<String> runningPackages) {
+ public void updateIconViewsRunningStates(Set<String> runningPackages,
+ Set<String> minimizedPackages) {
for (View iconView : getIconViews()) {
if (iconView instanceof BubbleTextView btv) {
- btv.updateRunningState(runningPackages.contains(btv.getTargetPackageName()));
+ btv.updateRunningState(
+ getRunningAppState(btv.getTargetPackageName(), runningPackages,
+ minimizedPackages));
}
}
}
+ private BubbleTextView.RunningAppState getRunningAppState(
+ String packageName,
+ Set<String> runningPackages,
+ Set<String> minimizedPackages) {
+ if (minimizedPackages.contains(packageName)) {
+ return BubbleTextView.RunningAppState.MINIMIZED;
+ }
+ if (runningPackages.contains(packageName)) {
+ return BubbleTextView.RunningAppState.RUNNING;
+ }
+ return BubbleTextView.RunningAppState.NOT_RUNNING;
+ }
+
/**
* Defers any updates to the UI for the setup wizard animation.
*/
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsControllerTest.kt
index 4fafde8..5b56710 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsControllerTest.kt
@@ -86,7 +86,8 @@
val newHotseatItems =
taskbarRunningAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
- assertThat(newHotseatItems.map { it?.targetPackage }).isEqualTo(hotseatPackages)
+ assertThat(newHotseatItems.map { it?.targetPackage })
+ .containsExactlyElementsIn(hotseatPackages)
}
@Test
@@ -119,7 +120,8 @@
RUNNING_APP_PACKAGE_1,
RUNNING_APP_PACKAGE_2,
)
- assertThat(newHotseatItems.map { it?.targetPackage }).isEqualTo(expectedPackages)
+ assertThat(newHotseatItems.map { it?.targetPackage })
+ .containsExactlyElementsIn(expectedPackages)
}
@Test
@@ -144,7 +146,8 @@
RUNNING_APP_PACKAGE_1,
RUNNING_APP_PACKAGE_2,
)
- assertThat(newHotseatItems.map { it?.targetPackage }).isEqualTo(expectedPackages)
+ assertThat(newHotseatItems.map { it?.targetPackage })
+ .containsExactlyElementsIn(expectedPackages)
}
@Test
@@ -155,7 +158,8 @@
whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
taskbarRunningAppsController.updateRunningApps()
- assertThat(taskbarRunningAppsController.runningApps).isEqualTo(emptySet<String>())
+ assertThat(taskbarRunningAppsController.runningApps).isEmpty()
+ assertThat(taskbarRunningAppsController.minimizedApps).isEmpty()
}
@Test
@@ -167,7 +171,28 @@
taskbarRunningAppsController.updateRunningApps()
assertThat(taskbarRunningAppsController.runningApps)
- .isEqualTo(setOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
+ .containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ assertThat(taskbarRunningAppsController.minimizedApps).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)
+ taskbarRunningAppsController.updateRunningApps()
+
+ assertThat(taskbarRunningAppsController.runningApps)
+ .containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3)
+ assertThat(taskbarRunningAppsController.minimizedApps)
+ .containsExactly(RUNNING_APP_PACKAGE_3)
}
private fun createHotseatItemsFromPackageNames(packageNames: List<String>): List<ItemInfo> {
@@ -180,11 +205,15 @@
return ArrayList(packageNames.map { createDesktopTaskInfo(packageName = it) })
}
- private fun createDesktopTaskInfo(packageName: String): RunningTaskInfo {
+ 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()
}
}
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b4f8a47..cb6cdc5 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -416,6 +416,9 @@
<dimen name="taskbar_running_app_indicator_height">0dp</dimen>
<dimen name="taskbar_running_app_indicator_width">0dp</dimen>
<dimen name="taskbar_running_app_indicator_top_margin">0dp</dimen>
+ <dimen name="taskbar_minimized_app_indicator_height">0dp</dimen>
+ <dimen name="taskbar_minimized_app_indicator_width">0dp</dimen>
+ <dimen name="taskbar_minimized_app_indicator_top_margin">0dp</dimen>
<!-- Transient taskbar (placeholders to compile in Launcher3 without Quickstep) -->
<dimen name="transient_taskbar_padding">0dp</dimen>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 2a8298f..7d09164 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -186,9 +186,20 @@
// These fields, related to showing running apps, are only used for Taskbar.
private final Size mRunningAppIndicatorSize;
private final int mRunningAppIndicatorTopMargin;
+ private final Size mMinimizedAppIndicatorSize;
+ private final int mMinimizedAppIndicatorTopMargin;
private final Paint mRunningAppIndicatorPaint;
private final Rect mRunningAppIconBounds = new Rect();
- private boolean mIsRunning;
+ private RunningAppState mRunningAppState;
+
+ /**
+ * Various options for the running state of an app.
+ */
+ public enum RunningAppState {
+ NOT_RUNNING,
+ RUNNING,
+ MINIMIZED,
+ }
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mStayPressed;
@@ -259,9 +270,16 @@
mRunningAppIndicatorSize = new Size(
getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_width),
getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_height));
+ mMinimizedAppIndicatorSize = new Size(
+ getResources().getDimensionPixelSize(R.dimen.taskbar_minimized_app_indicator_width),
+ getResources().getDimensionPixelSize(
+ R.dimen.taskbar_minimized_app_indicator_height));
mRunningAppIndicatorTopMargin =
getResources().getDimensionPixelSize(
R.dimen.taskbar_running_app_indicator_top_margin);
+ mMinimizedAppIndicatorTopMargin =
+ getResources().getDimensionPixelSize(
+ R.dimen.taskbar_minimized_app_indicator_top_margin);
mRunningAppIndicatorPaint = new Paint();
mRunningAppIndicatorPaint.setColor(getResources().getColor(
R.color.taskbar_running_app_indicator_color, context.getTheme()));
@@ -414,8 +432,8 @@
/** Updates whether the app this view represents is currently running. */
@UiThread
- public void updateRunningState(boolean isRunning) {
- mIsRunning = isRunning;
+ public void updateRunningState(RunningAppState runningAppState) {
+ mRunningAppState = runningAppState;
}
protected void setItemInfo(ItemInfoWithIcon itemInfo) {
@@ -667,18 +685,20 @@
/** Draws a line under the app icon if this is representing a running app in Desktop Mode. */
protected void drawRunningAppIndicatorIfNecessary(Canvas canvas) {
- if (!mIsRunning || mDisplay != DISPLAY_TASKBAR) {
+ if (mRunningAppState == RunningAppState.NOT_RUNNING || mDisplay != DISPLAY_TASKBAR) {
return;
}
getIconBounds(mRunningAppIconBounds);
// TODO(b/333872717): update color, shape, and size of indicator
- int indicatorTop = mRunningAppIconBounds.bottom + mRunningAppIndicatorTopMargin;
- canvas.drawRect(
- mRunningAppIconBounds.centerX() - mRunningAppIndicatorSize.getWidth() / 2,
- indicatorTop,
- mRunningAppIconBounds.centerX() + mRunningAppIndicatorSize.getWidth() / 2,
- indicatorTop + mRunningAppIndicatorSize.getHeight(),
- mRunningAppIndicatorPaint);
+ boolean isMinimized = mRunningAppState == RunningAppState.MINIMIZED;
+ int indicatorTop =
+ mRunningAppIconBounds.bottom + (isMinimized ? mMinimizedAppIndicatorTopMargin
+ : mRunningAppIndicatorTopMargin);
+ final Size indicatorSize =
+ isMinimized ? mMinimizedAppIndicatorSize : mRunningAppIndicatorSize;
+ canvas.drawRect(mRunningAppIconBounds.centerX() - indicatorSize.getWidth() / 2,
+ indicatorTop, mRunningAppIconBounds.centerX() + indicatorSize.getWidth() / 2,
+ indicatorTop + indicatorSize.getHeight(), mRunningAppIndicatorPaint);
}
@Override