diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java index 16980502cd..6bd1d758a6 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.taskbar; +import static com.android.launcher3.Flags.enableAltTabKqsFlatenning; import static com.android.launcher3.Flags.enableAltTabKqsOnConnectedDisplays; import android.app.ActivityManager; @@ -48,6 +49,7 @@ import java.util.List; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Handles initialization of the {@link KeyboardQuickSwitchViewController}. @@ -243,13 +245,39 @@ public final class KeyboardQuickSwitchController implements private void processLoadedTasks(List tasks, Set taskIdsToExclude) { mHasDesktopTask = false; mWasDesktopTaskFilteredOut = false; - if (mControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) { + + if (enableAltTabKqsFlatenning()) { + processLoadedTasksCombined(tasks, taskIdsToExclude); + } else if (mControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) { processLoadedTasksOnDesktop(tasks, taskIdsToExclude); } else { processLoadedTasksOutsideDesktop(tasks, taskIdsToExclude); } } + private void processLoadedTasksCombined(List tasks, Set taskIdsToExclude) { + List allTasks = tasks.stream() + .flatMap(task -> { + // In case of DesktopTasks, convert each contained task into a new DesktopTask + // this way the view controller will be able to show a thumbnail in KQS view. + if (task instanceof DesktopTask desktopTask) { + return desktopTask.getTasks().stream() + .map(singleTask -> new DesktopTask(desktopTask.getDeskId(), + desktopTask.getDisplayId(), List.of(singleTask))); + } + + return Stream.of(task); + }) + .filter(task -> !shouldExcludeTask(task, taskIdsToExclude)) + .toList(); + + // TODO(b/382769617): Sort tasks, including desktop and split, before limiting to max. + mTasks = allTasks.stream() + .limit(MAX_TASKS) + .toList(); + mNumHiddenTasks = Math.max(0, allTasks.size() - MAX_TASKS); + } + private void processLoadedTasksOutsideDesktop(List tasks, Set taskIdsToExclude) { // Only store MAX_TASK tasks, from most to least recent diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java index 515cfe2af0..fe93a26b94 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java @@ -47,11 +47,13 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.app.animation.Interpolators; import com.android.internal.jank.Cuj; +import com.android.launcher3.Flags; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; +import com.android.quickstep.util.DesktopTask; import com.android.quickstep.util.GroupTask; import com.android.quickstep.util.SingleTask; import com.android.quickstep.util.SplitTask; @@ -285,6 +287,10 @@ public class KeyboardQuickSwitchView extends ConstraintLayout { } else if (groupTask instanceof SingleTask singleTask) { task1 = singleTask.getTask(); task2 = null; + } else if (Flags.enableAltTabKqsFlatenning() + && groupTask instanceof DesktopTask desktopTask) { + task1 = desktopTask.getTasks().get(0); + task2 = null; } else { continue; } diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java index a312d5f70b..5add5c1a44 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java @@ -43,6 +43,7 @@ import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext; import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer; import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.util.DesktopTask; import com.android.quickstep.util.GroupTask; import com.android.quickstep.util.SingleTask; import com.android.quickstep.util.SlideInRemoteTransition; @@ -50,6 +51,7 @@ import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.systemui.shared.system.QuickStepContract; +import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason; import java.io.PrintWriter; @@ -266,9 +268,11 @@ public class KeyboardQuickSwitchViewController { onStartCallback, onFinishCallback), "SlideInTransition"); + SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get( + mKeyboardQuickSwitchView.getContext()); if (index == mKeyboardQuickSwitchView.getDesktopTaskIndex()) { UI_HELPER_EXECUTOR.execute(() -> - SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext()) + systemUiProxy .showDesktopApps( mKeyboardQuickSwitchView.getDisplay().getDisplayId(), slideInTransition)); @@ -284,20 +288,19 @@ public class KeyboardQuickSwitchViewController { // Ignore attempts to run the selected task if it is already running. return -1; } + + if (Flags.enableAltTabKqsFlatenning() + && tryLaunchingCombinedTask(task, slideInTransition, systemUiProxy)) { + return -1; + } + RemoteTransition remoteTransition = slideInTransition; boolean canUnminimizeDesktopTask = task instanceof SingleTask singleTask && mControllers.taskbarActivityContext.canUnminimizeDesktopTask( singleTask.getTask().key.id); if (mOnDesktop && canUnminimizeDesktopTask) { // This app is being unminimized - use our own transition runner. - remoteTransition = new RemoteTransition( - new DesktopAppLaunchTransition( - context, - UNMINIMIZE, - Cuj.CUJ_DESKTOP_MODE_KEYBOARD_QUICK_SWITCH_APP_LAUNCH, - MAIN_EXECUTOR - ), - "DesktopKeyboardQuickSwitchUnminimize"); + remoteTransition = getUnminimizeTransition(); } mControllers.taskbarActivityContext.handleGroupTaskLaunch( task, @@ -309,6 +312,61 @@ public class KeyboardQuickSwitchViewController { return -1; } + private boolean tryLaunchingCombinedTask(GroupTask task, RemoteTransition slideInTransition, + SystemUiProxy systemUiProxy) { + TaskbarActivityContext context = mControllers.taskbarActivityContext; + int taskId = task.getTasks().getFirst().key.id; + + // All DesktopTasks, irrespective of whether desktop mode is active, are launched here as + // the class DesktopTask is used in a special way by KQS view for showing thumbnails of + // freeform tasks. + if (task instanceof DesktopTask) { + boolean canUnminimizeDesktopTask = context.canUnminimizeDesktopTask(taskId); + runOnUiWithJankMonitoring(() -> { + if (!mOnDesktop) { + systemUiProxy.showDesktopApps(context.getDisplayId(), slideInTransition); + } + + systemUiProxy.showDesktopApp(taskId, + canUnminimizeDesktopTask ? getUnminimizeTransition() : null, + DesktopTaskToFrontReason.ALT_TAB); + }); + return true; + } else if (mOnDesktop && task instanceof SingleTask) { + // Use the special API if user wants to switch to a fullscreen app while in desktop. + runOnUiWithJankMonitoring( + () -> systemUiProxy.moveToFullscreen(taskId, + DesktopModeTransitionSource.KEYBOARD_SHORTCUT, slideInTransition)); + return true; + } + + // For all other cases, let TaskbarActivityContext handle launching the task. + return false; + } + + private void runOnUiWithJankMonitoring(Runnable runnable) { + Runnable onStartCallback = () -> InteractionJankMonitorWrapper.begin( + mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH); + Runnable onFinishCallback = () -> InteractionJankMonitorWrapper.end( + Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH); + UI_HELPER_EXECUTOR.execute(() -> { + onStartCallback.run(); + runnable.run(); + onFinishCallback.run(); + }); + } + + private RemoteTransition getUnminimizeTransition() { + return new RemoteTransition( + new DesktopAppLaunchTransition( + mControllers.taskbarActivityContext, + UNMINIMIZE, + Cuj.CUJ_DESKTOP_MODE_KEYBOARD_QUICK_SWITCH_APP_LAUNCH, + MAIN_EXECUTOR + ), + "DesktopKeyboardQuickSwitchUnminimize"); + } + private void onCloseComplete() { mCloseAnimation = null; // Reset the view callbacks to prevent `onDetachedFromWindow` getting called in response to diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt index cd87d93650..199283bd1b 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt @@ -692,7 +692,7 @@ class SystemUiProxy @Inject constructor(@ApplicationContext private val context: /** Tells SysUI to move the dragged bubble to full screen. */ fun moveDraggedBubbleToFullscreen(key: String, dropLocation: Point) { - executeWithErrorLog({ "Failed to call moveDraggedBubbleToFullscreen"}) { + executeWithErrorLog({ "Failed to call moveDraggedBubbleToFullscreen" }) { bubbles?.moveDraggedBubbleToFullscreen(key, dropLocation) } } @@ -1132,6 +1132,17 @@ class SystemUiProxy @Inject constructor(@ApplicationContext private val context: desktopMode?.showDesktopApp(taskId, transition, toFrontReason) } + /** Call shell to move to an existing fullscreen task (given by [taskId]) from desktop. */ + @JvmOverloads + fun moveToFullscreen( + taskId: Int, + desktopModeTransitionSource: DesktopModeTransitionSource, + remoteTransition: RemoteTransition? = null, + ) = + executeWithErrorLog({ "Failed call moveToFullscreen" }) { + desktopMode?.moveToFullscreen(taskId, desktopModeTransitionSource, remoteTransition) + } + /** Set a listener on shell to get updates about desktop task state */ fun setDesktopTaskListener(listener: IDesktopTaskListener?) { desktopTaskListener = listener