Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1096b70b authored by Gustav Sennton's avatar Gustav Sennton
Browse files

Minimize a Task whenever we reach the Desktop Task limit

- Introduce DesktopTasksLimiter to limit the number of visible Desktop
Tasks
- Keep track of minimized Desktop Tasks in DesktopModeTaskRepository
- Call into DesktopTasksLimiter from DesktopTasksController whenever we
  handle a transition that might cause us to hit the task limit.
  - to minimize a Task we reorder it to the bottom of the Desktop Tasks,
    placing it behind the Home Task. We mark the task as minimized in
    DesktopModeTaskRepository later when the transition is ready.
- Add a SystemProperty 'persist.wm.debug.desktop_max_task_limit' to
  control the maximum Task limit. To update the limit to X tasks, run:
  'adb shell setprop persist.wm.debug.desktop_max_task_limit X'

Bug: 332503075, 316115519
Test: unit tests
Change-Id: Ia34b67b4628d50e99347d9680e5a869ca666eaa3
parent b43e44ee
Loading
Loading
Loading
Loading
+24 −5
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -59,6 +60,7 @@ import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
@@ -517,23 +519,39 @@ public abstract class WMShellModule {
            LaunchAdjacentController launchAdjacentController,
            RecentsTransitionHandler recentsTransitionHandler,
            MultiInstanceHelper multiInstanceHelper,
            @ShellMainThread ShellExecutor mainExecutor
    ) {
            @ShellMainThread ShellExecutor mainExecutor,
            Optional<DesktopTasksLimiter> desktopTasksLimiter) {
        return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
                displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
                dragAndDropController, transitions, enterDesktopTransitionHandler,
                exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
                dragToDesktopTransitionHandler, desktopModeTaskRepository,
                desktopModeLoggerTransitionObserver, launchAdjacentController,
                recentsTransitionHandler, multiInstanceHelper, mainExecutor);
                recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter);
    }

    @WMSingleton
    @Provides
    static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter(
            Transitions transitions,
            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
            ShellTaskOrganizer shellTaskOrganizer) {
        if (!DesktopModeStatus.isEnabled() || !Flags.enableDesktopWindowingTaskLimit()) {
            return Optional.empty();
        }
        return Optional.of(
                new DesktopTasksLimiter(
                        transitions, desktopModeTaskRepository, shellTaskOrganizer));
    }


    @WMSingleton
    @Provides
    static DragToDesktopTransitionHandler provideDragToDesktopTransitionHandler(
            Context context,
            Transitions transitions,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            Optional<DesktopTasksLimiter> desktopTasksLimiter) {
        return new DragToDesktopTransitionHandler(context, transitions,
                rootTaskDisplayAreaOrganizer);
    }
@@ -541,7 +559,8 @@ public abstract class WMShellModule {
    @WMSingleton
    @Provides
    static EnterDesktopTaskTransitionHandler provideEnterDesktopModeTaskTransitionHandler(
            Transitions transitions) {
            Transitions transitions,
            Optional<DesktopTasksLimiter> desktopTasksLimiter) {
        return new EnterDesktopTaskTransitionHandler(transitions);
    }

+23 −0
Original line number Diff line number Diff line
@@ -76,6 +76,22 @@ public class DesktopModeStatus {
    private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
            "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);

    /**
     * Default value for {@code MAX_TASK_LIMIT}.
     */
    @VisibleForTesting
    public static final int DEFAULT_MAX_TASK_LIMIT = 4;

    // TODO(b/335131008): add a config-overlay field for the max number of tasks in Desktop Mode
    /**
     * Flag declaring the maximum number of Tasks to show in Desktop Mode at any one time.
     *
     * <p> The limit does NOT affect Picture-in-Picture, Bubbles, or System Modals (like a screen
     * recording window, or Bluetooth pairing window).
     */
    private static final int MAX_TASK_LIMIT = SystemProperties.getInt(
            "persist.wm.debug.desktop_max_task_limit", DEFAULT_MAX_TASK_LIMIT);

    /**
     * Return {@code true} if desktop windowing is enabled
     */
@@ -123,6 +139,13 @@ public class DesktopModeStatus {
        return ENFORCE_DEVICE_RESTRICTIONS;
    }

    /**
     * Return the maximum limit on the number of Tasks to show in Desktop Mode at any one time.
     */
    static int getMaxTaskLimit() {
        return MAX_TASK_LIMIT;
    }

    /**
     * Return {@code true} if the current device supports desktop mode.
     */
+47 −1
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ class DesktopModeTaskRepository {
         */
        val activeTasks: ArraySet<Int> = ArraySet(),
        val visibleTasks: ArraySet<Int> = ArraySet(),
        val minimizedTasks: ArraySet<Int> = ArraySet(),
        var stashed: Boolean = false
    )

@@ -202,6 +203,13 @@ class DesktopModeTaskRepository {
        }
    }

    /** Return whether the given Task is minimized. */
    fun isMinimizedTask(taskId: Int): Boolean {
        return displayData.valueIterator().asSequence().any { data ->
            data.minimizedTasks.contains(taskId)
        }
    }

    /**
     *  Check if a task with the given [taskId] is the only active task on its display
     */
@@ -218,6 +226,25 @@ class DesktopModeTaskRepository {
        return ArraySet(displayData[displayId]?.activeTasks)
    }

    /**
     * Returns whether Desktop Mode is currently showing any tasks, i.e. whether any Desktop Tasks
     * are visible.
     */
    fun isDesktopModeShowing(displayId: Int): Boolean = getVisibleTaskCount(displayId) > 0

    /**
     * Returns a list of Tasks IDs representing all active non-minimized Tasks on the given display,
     * ordered from front to back.
     */
    fun getActiveNonMinimizedTasksOrderedFrontToBack(displayId: Int): List<Int> {
        val activeTasks = getActiveTasks(displayId)
        val allTasksInZOrder = getFreeformTasksInZOrder()
        return activeTasks
                // Don't show already minimized Tasks
                .filter { taskId -> !isMinimizedTask(taskId) }
                .sortedBy { taskId -> allTasksInZOrder.indexOf(taskId) }
    }

    /**
     * Get a list of freeform tasks, ordered from top-bottom (top at index 0).
     */
@@ -255,6 +282,7 @@ class DesktopModeTaskRepository {
        val prevCount = getVisibleTaskCount(displayId)
        if (visible) {
            displayData.getOrCreate(displayId).visibleTasks.add(taskId)
            unminimizeTask(displayId, taskId)
        } else {
            displayData[displayId]?.visibleTasks?.remove(taskId)
        }
@@ -312,6 +340,24 @@ class DesktopModeTaskRepository {
        freeformTasksInZOrder.add(0, taskId)
    }

    /** Mark a Task as minimized. */
    fun minimizeTask(displayId: Int, taskId: Int) {
        KtProtoLog.v(
                WM_SHELL_DESKTOP_MODE,
                "DesktopModeTaskRepository: minimize Task: display=%d, task=%d",
                displayId, taskId)
        displayData.getOrCreate(displayId).minimizedTasks.add(taskId)
    }

    /** Mark a Task as non-minimized. */
    fun unminimizeTask(displayId: Int, taskId: Int) {
        KtProtoLog.v(
                WM_SHELL_DESKTOP_MODE,
                "DesktopModeTaskRepository: unminimize Task: display=%d, task=%d",
                displayId, taskId)
        displayData[displayId]?.minimizedTasks?.remove(taskId)
    }

    /**
     * Remove the task from the ordered list.
     */
@@ -325,7 +371,7 @@ class DesktopModeTaskRepository {
        boundsBeforeMaximizeByTaskId.remove(taskId)
        KtProtoLog.d(
            WM_SHELL_DESKTOP_MODE,
            "DesktopTaskRepo: remaining freeform tasks: " + freeformTasksInZOrder.toDumpString()
            "DesktopTaskRepo: remaining freeform tasks: %s", freeformTasksInZOrder.toDumpString(),
        )
    }

+99 −27
Original line number Diff line number Diff line
@@ -88,6 +88,7 @@ import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import com.android.wm.shell.windowdecor.extension.isFreeform
import com.android.wm.shell.windowdecor.extension.isFullscreen
import java.io.PrintWriter
import java.util.Optional
import java.util.concurrent.Executor
import java.util.function.Consumer

@@ -113,7 +114,8 @@ class DesktopTasksController(
        private val launchAdjacentController: LaunchAdjacentController,
        private val recentsTransitionHandler: RecentsTransitionHandler,
        private val multiInstanceHelper: MultiInstanceHelper,
        @ShellMainThread private val mainExecutor: ShellExecutor
        @ShellMainThread private val mainExecutor: ShellExecutor,
        private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler,
    DragAndDropController.DragAndDropListener {

@@ -341,11 +343,13 @@ class DesktopTasksController(
        )
        exitSplitIfApplicable(wct, task)
        // Bring other apps to front first
        bringDesktopAppsToFront(task.displayId, wct)
        val taskToMinimize =
                bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
        addMoveToDesktopChanges(wct, task)

        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            enterDesktopTaskTransitionHandler.moveToDesktop(wct)
            val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct)
            addPendingMinimizeTransition(transition, taskToMinimize)
        } else {
            shellTaskOrganizer.applyTransaction(wct)
        }
@@ -382,10 +386,14 @@ class DesktopTasksController(
        )
        val wct = WindowContainerTransaction()
        exitSplitIfApplicable(wct, taskInfo)
        bringDesktopAppsToFront(taskInfo.displayId, wct)
        moveHomeTaskToFront(wct)
        val taskToMinimize =
                bringDesktopAppsToFrontBeforeShowingNewTask(
                        taskInfo.displayId, wct, taskInfo.taskId)
        addMoveToDesktopChanges(wct, taskInfo)
        wct.setBounds(taskInfo.token, freeformBounds)
        dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct)
        val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct)
        transition?.let { addPendingMinimizeTransition(it, taskToMinimize) }
    }

    /**
@@ -507,8 +515,10 @@ class DesktopTasksController(

        val wct = WindowContainerTransaction()
        wct.reorder(taskInfo.token, true)
        val taskToMinimize = addAndGetMinimizeChangesIfNeeded(taskInfo.displayId, wct, taskInfo)
        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
            val transition = transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
            addPendingMinimizeTransition(transition, taskToMinimize)
        } else {
            shellTaskOrganizer.applyTransaction(wct)
        }
@@ -688,9 +698,20 @@ class DesktopTasksController(
            ?: WINDOWING_MODE_UNDEFINED
    }

    private fun bringDesktopAppsToFront(displayId: Int, wct: WindowContainerTransaction) {
        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: bringDesktopAppsToFront")
        val activeTasks = desktopModeTaskRepository.getActiveTasks(displayId)
    private fun bringDesktopAppsToFrontBeforeShowingNewTask(
            displayId: Int,
            wct: WindowContainerTransaction,
            newTaskIdInFront: Int
    ): RunningTaskInfo? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront)

    private fun bringDesktopAppsToFront(
            displayId: Int,
            wct: WindowContainerTransaction,
            newTaskIdInFront: Int? = null
    ): RunningTaskInfo? {
        KtProtoLog.v(WM_SHELL_DESKTOP_MODE,
                "DesktopTasksController: bringDesktopAppsToFront, newTaskIdInFront=%s",
                newTaskIdInFront ?: "null")

        if (Flags.enableDesktopWindowingWallpaperActivity()) {
            // Add translucent wallpaper activity to show the wallpaper underneath
@@ -700,13 +721,21 @@ class DesktopTasksController(
            moveHomeTaskToFront(wct)
        }

        // Then move other tasks on top of it
        val allTasksInZOrder = desktopModeTaskRepository.getFreeformTasksInZOrder()
        activeTasks
            // Sort descending as the top task is at index 0. It should be ordered to top last
            .sortedByDescending { taskId -> allTasksInZOrder.indexOf(taskId) }
        val nonMinimizedTasksOrderedFrontToBack =
                desktopModeTaskRepository.getActiveNonMinimizedTasksOrderedFrontToBack(displayId)
        // If we're adding a new Task we might need to minimize an old one
        val taskToMinimize: RunningTaskInfo? =
                if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
                    desktopTasksLimiter.get().getTaskToMinimizeIfNeeded(
                            nonMinimizedTasksOrderedFrontToBack, newTaskIdInFront)
                } else { null }
        nonMinimizedTasksOrderedFrontToBack
                // If there is a Task to minimize, let it stay behind the Home Task
                .filter { taskId -> taskId != taskToMinimize?.taskId }
                .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
                .reversed() // Start from the back so the front task is brought forward last
                .forEach { task -> wct.reorder(task.token, true /* onTop */) }
        return taskToMinimize
    }

    private fun moveHomeTaskToFront(wct: WindowContainerTransaction) {
@@ -824,13 +853,13 @@ class DesktopTasksController(
            when {
                request.type == TRANSIT_TO_BACK -> handleBackNavigation(task)
                // If display has tasks stashed, handle as stashed launch
                task.isStashed -> handleStashedTaskLaunch(task)
                task.isStashed -> handleStashedTaskLaunch(task, transition)
                // Check if the task has a top transparent activity
                shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task)
                // Check if fullscreen task should be updated
                task.isFullscreen -> handleFullscreenTaskLaunch(task)
                task.isFullscreen -> handleFullscreenTaskLaunch(task, transition)
                // Check if freeform task should be updated
                task.isFreeform -> handleFreeformTaskLaunch(task)
                task.isFreeform -> handleFreeformTaskLaunch(task, transition)
                else -> {
                    null
                }
@@ -878,10 +907,12 @@ class DesktopTasksController(
                } ?: false
    }

    private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
    private fun handleFreeformTaskLaunch(
            task: RunningTaskInfo,
            transition: IBinder
    ): WindowContainerTransaction? {
        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
        val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
        if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
        if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
            KtProtoLog.d(
                    WM_SHELL_DESKTOP_MODE,
                    "DesktopTasksController: switch freeform task to fullscreen oon transition" +
@@ -892,13 +923,23 @@ class DesktopTasksController(
                addMoveToFullscreenChanges(wct, task)
            }
        }
        // Desktop Mode is showing and we're launching a new Task - we might need to minimize
        // a Task.
        val wct = WindowContainerTransaction()
        val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
        if (taskToMinimize != null) {
            addPendingMinimizeTransition(transition, taskToMinimize)
            return wct
        }
        return null
    }

    private fun handleFullscreenTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
    private fun handleFullscreenTaskLaunch(
            task: RunningTaskInfo,
            transition: IBinder
    ): WindowContainerTransaction? {
        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
        val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
        if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
        if (desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
            KtProtoLog.d(
                    WM_SHELL_DESKTOP_MODE,
                    "DesktopTasksController: switch fullscreen task to freeform on transition" +
@@ -907,21 +948,30 @@ class DesktopTasksController(
            )
            return WindowContainerTransaction().also { wct ->
                addMoveToDesktopChanges(wct, task)
                // Desktop Mode is already showing and we're launching a new Task - we might need to
                // minimize another Task.
                val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
                addPendingMinimizeTransition(transition, taskToMinimize)
            }
        }
        return null
    }

    private fun handleStashedTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction {
    private fun handleStashedTaskLaunch(
            task: RunningTaskInfo,
            transition: IBinder
    ): WindowContainerTransaction {
        KtProtoLog.d(
                WM_SHELL_DESKTOP_MODE,
                "DesktopTasksController: launch apps with stashed on transition taskId=%d",
                task.taskId
        )
        val wct = WindowContainerTransaction()
        bringDesktopAppsToFront(task.displayId, wct)
        val taskToMinimize =
                bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
        addMoveToDesktopChanges(wct, task)
        desktopModeTaskRepository.setStashed(task.displayId, false)
        addPendingMinimizeTransition(transition, taskToMinimize)
        return wct
    }

@@ -1002,6 +1052,28 @@ class DesktopTasksController(
        wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
    }

    /** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */
    private fun addAndGetMinimizeChangesIfNeeded(
            displayId: Int,
            wct: WindowContainerTransaction,
            newTaskInfo: RunningTaskInfo
    ): RunningTaskInfo? {
        if (!desktopTasksLimiter.isPresent) return null
        return desktopTasksLimiter.get().addAndGetMinimizeTaskChangesIfNeeded(
                displayId, wct, newTaskInfo)
    }

    private fun addPendingMinimizeTransition(
            transition: IBinder,
            taskToMinimize: RunningTaskInfo?
    ) {
        if (taskToMinimize == null) return
        desktopTasksLimiter.ifPresent {
            it.addPendingMinimizeChange(
                    transition, taskToMinimize.displayId, taskToMinimize.taskId)
        }
    }

    /** Enter split by using the focused desktop task in given `displayId`. */
    fun enterSplit(
        displayId: Int,
+217 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading