diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java index cb16345eec..b928de0617 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java @@ -28,6 +28,7 @@ import com.android.launcher3.R; import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext; import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer; import com.android.launcher3.util.TouchController; +import com.android.quickstep.RecentsFilterState; import com.android.quickstep.RecentsModel; import com.android.quickstep.util.DesktopTask; import com.android.quickstep.util.GroupTask; @@ -153,6 +154,8 @@ public final class KeyboardQuickSwitchController implements // Skip the task reload if the list is not changed. if (!mModel.isTaskListValid(mTaskListChangeId) || !taskIdsToExclude.equals( mExcludedTaskIds)) { + final boolean shouldShowDesktopTasks = mControllers.taskbarDesktopModeController + .shouldShowDesktopTasksInTaskbar(); mExcludedTaskIds = taskIdsToExclude; mTaskListChangeId = mModel.getTasks((tasks) -> { processLoadedTasks(tasks, taskIdsToExclude); @@ -162,7 +165,8 @@ public final class KeyboardQuickSwitchController implements currentFocusIndexOverride, mHasDesktopTask, mWasDesktopTaskFilteredOut); - }); + }, shouldShowDesktopTasks ? RecentsFilterState.EMPTY_FILTER + : RecentsFilterState.getEmptyDesktopTaskFilter()); } mQuickSwitchViewController.updateLayoutForSurface(wasOpenedFromTaskbar, @@ -188,8 +192,8 @@ public final class KeyboardQuickSwitchController implements mQuickSwitchViewController = new KeyboardQuickSwitchViewController( mControllers, mOverlayContext, keyboardQuickSwitchView, mControllerCallbacks); - final boolean onDesktop = mControllers.taskbarDesktopModeController - .getAreDesktopTasksVisibleAndNotInOverview(); + final boolean shouldShowDesktopTasks = mControllers.taskbarDesktopModeController + .shouldShowDesktopTasksInTaskbar(); if (mModel.isTaskListValid(mTaskListChangeId) && taskIdsToExclude.equals(mExcludedTaskIds)) { @@ -201,7 +205,7 @@ public final class KeyboardQuickSwitchController implements /* updateTasks= */ false, currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning() ? 0 : currentFocusedIndex, - onDesktop, + shouldShowDesktopTasks, mHasDesktopTask, mWasDesktopTaskFilteredOut, wasOpenedFromTaskbar); @@ -219,11 +223,12 @@ public final class KeyboardQuickSwitchController implements /* updateTasks= */ true, currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning() ? 0 : currentFocusedIndex, - onDesktop, + shouldShowDesktopTasks, mHasDesktopTask, mWasDesktopTaskFilteredOut, wasOpenedFromTaskbar); - }); + }, shouldShowDesktopTasks ? RecentsFilterState.EMPTY_FILTER + : RecentsFilterState.getEmptyDesktopTaskFilter()); } private boolean shouldExcludeTask(GroupTask task, Set taskIdsToExclude) { @@ -233,7 +238,7 @@ public final class KeyboardQuickSwitchController implements private void processLoadedTasks(List tasks, Set taskIdsToExclude) { mHasDesktopTask = false; mWasDesktopTaskFilteredOut = false; - if (mControllers.taskbarDesktopModeController.getAreDesktopTasksVisibleAndNotInOverview()) { + if (mControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) { processLoadedTasksOnDesktop(tasks, taskIdsToExclude); } else { processLoadedTasksOutsideDesktop(tasks, taskIdsToExclude); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index a0f3948686..6f88baf3a8 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -368,7 +368,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext { new KeyboardQuickSwitchController(), new TaskbarPinningController(this), bubbleControllersOptional, - new TaskbarDesktopModeController(DesktopVisibilityController.INSTANCE.get(this))); + new TaskbarDesktopModeController(this, + DesktopVisibilityController.INSTANCE.get(this))); mLauncherPrefs = LauncherPrefs.get(this); } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt index a7c73817a2..8806bc69f4 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt @@ -16,13 +16,16 @@ package com.android.launcher3.taskbar +import android.content.Context import com.android.launcher3.statehandlers.DesktopVisibilityController import com.android.launcher3.statehandlers.DesktopVisibilityController.TaskbarDesktopModeListener import com.android.launcher3.taskbar.TaskbarBackgroundRenderer.Companion.MAX_ROUNDNESS +import com.android.launcher3.util.DisplayController /** Handles Taskbar in Desktop Windowing mode. */ class TaskbarDesktopModeController( - private val desktopVisibilityController: DesktopVisibilityController + private val context: Context, + private val desktopVisibilityController: DesktopVisibilityController, ) : TaskbarDesktopModeListener { private lateinit var taskbarControllers: TaskbarControllers private lateinit var taskbarSharedState: TaskbarSharedState @@ -45,6 +48,12 @@ class TaskbarDesktopModeController( taskbarControllers.taskbarCornerRoundness.animateToValue(cornerRadius).start() } + fun shouldShowDesktopTasksInTaskbar(): Boolean { + return desktopVisibilityController.areDesktopTasksVisible() || + DisplayController.showLockedTaskbarOnHome(context) && + taskbarControllers.taskbarStashController.isOnHome + } + fun getTaskbarCornerRoundness(doesAnyTaskRequireTaskbarRounding: Boolean): Float { return if (doesAnyTaskRequireTaskbarRounding) { MAX_ROUNDNESS diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt index 4afabdeef6..cba2af5ebd 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt @@ -26,6 +26,7 @@ import com.android.launcher3.model.data.TaskItemInfo import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController import com.android.launcher3.util.CancellableTask +import com.android.quickstep.RecentsFilterState import com.android.quickstep.RecentsModel import com.android.quickstep.util.DesktopTask import com.android.quickstep.util.GroupTask @@ -119,7 +120,7 @@ class TaskbarRecentAppsController(context: Context, private val recentsModel: Re get() { if ( !canShowRunningApps || - !controllers.taskbarDesktopModeController.areDesktopTasksVisible + !controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar() ) { return emptySet() } @@ -135,7 +136,7 @@ class TaskbarRecentAppsController(context: Context, private val recentsModel: Re get() { if ( !canShowRunningApps || - !controllers.taskbarDesktopModeController.areDesktopTasksVisible + !controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar() ) { return emptySet() } @@ -171,10 +172,10 @@ class TaskbarRecentAppsController(context: Context, private val recentsModel: Re /** Called to update hotseatItems, in order to de-dupe them from Recent/Running tasks later. */ fun updateHotseatItemInfos(hotseatItems: Array): Array { // Ignore predicted apps - we show running or recent apps instead. - val areDesktopTasksVisible = controllers.taskbarDesktopModeController.areDesktopTasksVisible + val showDesktopTasks = + controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar() val removePredictions = - (areDesktopTasksVisible && canShowRunningApps) || - (!areDesktopTasksVisible && canShowRecentApps) + (showDesktopTasks && canShowRunningApps) || (!showDesktopTasks && canShowRecentApps) if (!removePredictions) { shownHotseatItems = hotseatItems.filterNotNull() onRecentsOrHotseatChanged() @@ -186,7 +187,7 @@ class TaskbarRecentAppsController(context: Context, private val recentsModel: Re .filter { itemInfo -> !itemInfo.isPredictedItem } .toMutableList() - if (areDesktopTasksVisible && canShowRunningApps) { + if (showDesktopTasks && canShowRunningApps) { shownHotseatItems = updateHotseatItemsFromRunningTasks( getOrderedAndWrappedDesktopTasks(), @@ -210,19 +211,24 @@ class TaskbarRecentAppsController(context: Context, private val recentsModel: Re private fun reloadRecentTasksIfNeeded() { if (!recentsModel.isTaskListValid(taskListChangeId)) { taskListChangeId = - recentsModel.getTasks { tasks -> - allRecentTasks = tasks - val oldRunningTaskdIds = runningTaskIds - val oldMinimizedTaskIds = minimizedTaskIds - desktopTask = allRecentTasks.filterIsInstance().firstOrNull() - val runningTasksChanged = oldRunningTaskdIds != runningTaskIds - val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds - if ( - onRecentsOrHotseatChanged() || runningTasksChanged || minimizedTasksChanged - ) { - controllers.taskbarViewController.commitRunningAppsToUI() - } - } + recentsModel.getTasks( + { tasks -> + allRecentTasks = tasks + val oldRunningTaskdIds = runningTaskIds + val oldMinimizedTaskIds = minimizedTaskIds + desktopTask = allRecentTasks.filterIsInstance().firstOrNull() + val runningTasksChanged = oldRunningTaskdIds != runningTaskIds + val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds + if ( + onRecentsOrHotseatChanged() || + runningTasksChanged || + minimizedTasksChanged + ) { + controllers.taskbarViewController.commitRunningAppsToUI() + } + }, + RecentsFilterState.EMPTY_FILTER, + ) } } @@ -235,7 +241,7 @@ class TaskbarRecentAppsController(context: Context, private val recentsModel: Re val oldShownTasks = shownTasks orderedRunningTaskIds = updateOrderedRunningTaskIds() shownTasks = - if (controllers.taskbarDesktopModeController.areDesktopTasksVisible) { + if (controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) { computeShownRunningTasks() } else { computeShownRecentTasks() diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java index 502c001681..025ad87edc 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -269,6 +269,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba private final long mTaskbarBackgroundDuration; private boolean mUserIsNotGoingHome = false; + private final boolean mInAppStateAffectsDesktopTasksVisibilityInTaskbar; + // Evaluate whether the handle should be stashed private final LongPredicate mIsStashedPredicate = flags -> { boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP); @@ -293,8 +295,17 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity); mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class); - mTaskbarBackgroundDuration = - activity.getResources().getInteger(R.integer.taskbar_background_duration); + // Taskbar, via `TaskbarDesktopModeController`, depends on `TaskbarStashController` state to + // determine whether desktop tasks should be shown because taskbar is pinned on the home + // screen for freeform windowing displays. In this case, list of items shown in the taskbar + // needs to be updated when in-app state changes. + // TODO(b/390665752): Feature to "lock" pinned taskbar to home screen will be superseded by + // pinning, in other launcher states, at which point this variable can be removed. + mInAppStateAffectsDesktopTasksVisibilityInTaskbar = + DisplayController.showLockedTaskbarOnHome(mActivity); + + mTaskbarBackgroundDuration = activity.getResources().getInteger( + R.integer.taskbar_background_duration); if (mActivity.isPhoneMode()) { mUnstashedHeight = mActivity.getResources().getDimensionPixelSize( R.dimen.taskbar_phone_size); @@ -1237,6 +1248,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba if (hasAnyFlag(changedFlags, FLAG_IN_OVERVIEW | FLAG_IN_APP)) { mControllers.runAfterInit(() -> mControllers.taskbarInsetsController .onTaskbarOrBubblebarWindowHeightOrInsetsChanged()); + if (mInAppStateAffectsDesktopTasksVisibilityInTaskbar) { + mControllers.runAfterInit( + () -> mControllers.taskbarViewController.commitRunningAppsToUI()); + } } mActivity.applyForciblyShownFlagWhileTransientTaskbarUnstashed(!isStashedInApp()); } diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index ee4ee38b5f..379d71b472 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -419,10 +419,6 @@ public class RecentTasksList { private @Nullable DesktopTask createDesktopTask(GroupedTaskInfo recentTaskInfo) { ArrayList tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size()); int[] minimizedTaskIds = recentTaskInfo.getMinimizedTaskIds(); - if (minimizedTaskIds.length == recentTaskInfo.getTaskInfoList().size()) { - // All Tasks are minimized -> don't create a DesktopTask - return null; - } for (TaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) { Task.TaskKey key = new Task.TaskKey(taskInfo); Task task = Task.from(key, taskInfo, false); diff --git a/quickstep/src/com/android/quickstep/RecentsFilterState.java b/quickstep/src/com/android/quickstep/RecentsFilterState.java index 70d569603a..c4b0f252c3 100644 --- a/quickstep/src/com/android/quickstep/RecentsFilterState.java +++ b/quickstep/src/com/android/quickstep/RecentsFilterState.java @@ -19,6 +19,7 @@ package com.android.quickstep; import androidx.annotation.Nullable; import com.android.quickstep.util.GroupTask; +import com.android.quickstep.views.TaskViewType; import com.android.systemui.shared.recents.model.Task; import java.util.HashMap; @@ -38,7 +39,7 @@ public class RecentsFilterState { public static final int MIN_FILTERING_TASK_COUNT = 2; // default filter that returns true for any input - public static final Predicate DEFAULT_FILTER = (groupTask -> true); + public static final Predicate EMPTY_FILTER = (groupTask -> true); // the package name to filter recent tasks by @Nullable @@ -116,14 +117,37 @@ public class RecentsFilterState { * Returns a predicate for filtering out GroupTasks by package name. * * @param packageName package name to filter GroupTasks by - * if null, Predicate always returns true. + * if null, Predicate filters out desktop tasks with no non-minimized tasks. */ public static Predicate getFilter(@Nullable String packageName) { if (packageName == null) { - return DEFAULT_FILTER; + return getEmptyDesktopTaskFilter(); } - return (groupTask) -> (groupTask.containsPackage(packageName)); + return (groupTask) -> (groupTask.containsPackage(packageName) + && !isDestopTaskWithMinimizedTasksOnly(groupTask)); + } + + /** + * Returns a predicate that filters out desk tasks that contain no non-minimized desktop tasks. + */ + public static Predicate getEmptyDesktopTaskFilter() { + return (groupTask -> !isDestopTaskWithMinimizedTasksOnly(groupTask)); + } + + /** + * Whether the provided task is a desktop task with no non-minimized tasks - returns true if the + * desktop task has no tasks at all. + * + * @param groupTask The group task to check. + */ + static boolean isDestopTaskWithMinimizedTasksOnly(GroupTask groupTask) { + if (groupTask.taskViewType != TaskViewType.DESKTOP) { + return false; + } + return groupTask.getTasks().stream() + .filter(task -> !task.isMinimized) + .toList().isEmpty(); } /** diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index 6498b7add8..dc5d59f3fb 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -154,7 +154,7 @@ public class RecentsModel implements RecentTasksDataSource, TaskStackChangeListe /** * Fetches the list of recent tasks. Tasks are ordered by recency, with the latest active tasks - * at the end of the list. + * at the end of the list. Filters out desktop tasks that contain no non-minimized tasks. * * @param callback The callback to receive the task plan once its complete or null. This is * always called on the UI thread. @@ -163,7 +163,7 @@ public class RecentsModel implements RecentTasksDataSource, TaskStackChangeListe @Override public int getTasks(@Nullable Consumer> callback) { return mTaskList.getTasks(false /* loadKeysOnly */, callback, - RecentsFilterState.DEFAULT_FILTER); + RecentsFilterState.getEmptyDesktopTaskFilter()); } /** diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt index 8d8e62e9fa..8805cb1191 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt @@ -147,7 +147,7 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { @Test fun canShowRunningAndRecentAppsIsFalseAfterInit_getTasksOnlyCalledInInit() { // getTasks() should have been called once from init(). - verify(mockRecentsModel, times(1)).getTasks(any>>()) + verify(mockRecentsModel, times(1)).getTasks(any>>(), any()) recentAppsController.canShowRunningApps = false recentAppsController.canShowRecentApps = false prepareHotseatAndRunningAndRecentApps( @@ -156,7 +156,7 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2), ) // Verify that getTasks() was not called again after the init(). - verify(mockRecentsModel, times(1)).getTasks(any>>()) + verify(mockRecentsModel, times(1)).getTasks(any>>(), any()) } @Test @@ -881,7 +881,7 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { taskListChangeId } .whenever(mockRecentsModel) - .getTasks(any>>()) + .getTasks(any>>(), any()) recentTasksChangedListener?.onRecentTasksChanged() } @@ -945,7 +945,7 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { } private fun setInDesktopMode(inDesktopMode: Boolean) { - whenever(taskbarControllers.taskbarDesktopModeController.areDesktopTasksVisible) + whenever(taskbarControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) .thenReturn(inDesktopMode) } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java index ccfe36d6b9..4a7c537b3d 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java @@ -21,6 +21,8 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.TestCase.assertNull; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -160,12 +162,15 @@ public class RecentTasksListTest { List actualFreeformTasks = taskList.get(0).getTasks(); assertEquals(3, actualFreeformTasks.size()); assertEquals(1, actualFreeformTasks.get(0).key.id); + assertFalse(actualFreeformTasks.get(0).isMinimized); assertEquals(4, actualFreeformTasks.get(1).key.id); + assertFalse(actualFreeformTasks.get(1).isMinimized); assertEquals(5, actualFreeformTasks.get(2).key.id); + assertFalse(actualFreeformTasks.get(2).isMinimized); } @Test - public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask() + public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_createDesktopTask() throws Exception { List tasks = Arrays.asList( createRecentTaskInfo(1 /* taskId */), @@ -180,7 +185,16 @@ public class RecentTasksListTest { List taskList = mRecentTasksList.loadTasksInBackground( Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */); - assertEquals(0, taskList.size()); + assertEquals(1, taskList.size()); + assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType); + List actualFreeformTasks = taskList.get(0).getTasks(); + assertEquals(3, actualFreeformTasks.size()); + assertEquals(1, actualFreeformTasks.get(0).key.id); + assertTrue(actualFreeformTasks.get(0).isMinimized); + assertEquals(4, actualFreeformTasks.get(1).key.id); + assertTrue(actualFreeformTasks.get(1).isMinimized); + assertEquals(5, actualFreeformTasks.get(2).key.id); + assertTrue(actualFreeformTasks.get(2).isMinimized); } private TaskInfo createRecentTaskInfo(int taskId) {