diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 2021a0b96a..08d36d8d87 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -358,6 +358,9 @@ 4dp 14dp 2dp + 2dp + 12dp + 2dp 12dp diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt index 3649c4ebc5..d4bef28bf8 100644 --- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt @@ -47,6 +47,7 @@ class DesktopTaskbarRunningAppsController( private var apps: Array? = null private var allRunningDesktopAppInfos: List? = null + private var allMinimizedDesktopAppInfos: List? = null private val desktopVisibilityController: DesktopVisibilityController? get() = desktopVisibilityControllerProvider() @@ -95,6 +96,13 @@ class DesktopTaskbarRunningAppsController( return allRunningDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet() } + override fun getMinimizedApps(): Set { + if (!isInDesktopMode) { + return emptySet() + } + return allMinimizedDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet() + } + @VisibleForTesting public override fun updateRunningApps() { if (!isInDesktopMode) { @@ -102,10 +110,34 @@ class DesktopTaskbarRunningAppsController( 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, + runningAppInfo: List, + ) { + 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, hotseatItems: List @@ -116,15 +148,10 @@ class DesktopTaskbarRunningAppsController( .map { WorkspaceItemInfo(it) } } - private fun getRunningDesktopAppInfos(): List { - return getAppInfosFromRunningTasks( - recentsModel.runningTasks - .filter { taskInfo: RunningTaskInfo -> - taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM - } - .toList() - ) - } + private fun getDesktopRunningTasks(): List = + 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): List { @@ -138,9 +165,10 @@ class DesktopTaskbarRunningAppsController( .filterNotNull() } - private fun SparseArray.toList(): List { - return valueIterator().asSequence().toList() - } + private fun getAppInfosFromRunningTask(task: RunningTaskInfo): AppInfo? = + apps?.firstOrNull { it.targetPackage == task.realActivity?.packageName } + + private fun SparseArray.toList(): List = 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 9f24d38664..35e1c7baa9 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java @@ -68,7 +68,7 @@ public class TaskbarModelCallbacks implements // 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 @@ public class TaskbarModelCallbacks implements hotseatItemInfos = mControllers.taskbarRecentAppsController .updateHotseatItemInfos(hotseatItemInfos); Set runningPackages = mControllers.taskbarRecentAppsController.getRunningApps(); + Set 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 runningPackages) { + private void commitHotseatItemUpdates(ItemInfo[] hotseatItemInfos, Set runningPackages, + Set 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 a29c74bf06..606ba5b633 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java @@ -69,4 +69,9 @@ public class TaskbarRecentAppsController { public Set getRunningApps() { return emptySet(); } + + /** Returns the set of apps whose tasks are all minimized. */ + public Set getMinimizedApps() { + return emptySet(); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index 93814b7008..23495adcf7 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -510,14 +510,30 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar } /** Updates which icons are marked as running given the Set of currently running packages. */ - public void updateIconViewsRunningStates(Set runningPackages) { + public void updateIconViewsRunningStates(Set runningPackages, + Set 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 runningPackages, + Set 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 4fafde8e80..5b567101b6 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 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() { 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 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() { 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 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() { 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 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() { whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks) taskbarRunningAppsController.updateRunningApps() - assertThat(taskbarRunningAppsController.runningApps).isEqualTo(emptySet()) + assertThat(taskbarRunningAppsController.runningApps).isEmpty() + assertThat(taskbarRunningAppsController.minimizedApps).isEmpty() } @Test @@ -167,7 +171,28 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() { 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): List { @@ -180,11 +205,15 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() { 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 b4f8a475e0..cb6cdc5b7d 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -416,6 +416,9 @@ 0dp 0dp 0dp + 0dp + 0dp + 0dp 0dp diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 2a8298f694..7d09164feb 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -186,9 +186,20 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, // 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 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, 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 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, /** 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 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, /** 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