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

Commit ac886d87 authored by Gustav Sennton's avatar Gustav Sennton
Browse files

Split window limit minimize Change into its own transition

Before this CL we would add minimize changes to existing app launch
transitions through DTC#handleRequest(). With this CL we instead use
runOnIdle() (called from onTransitionReady()) to check whether to
launch a new minimize transition.
The benefit of using runOnIdle() instead of handleRequest() is that in
runOnIdle() we have information about all the collected Changes in the
transition so we can make a more informed decision on whether to
minimize. This new approach supports edge-cases such as launching task
trampolines and opening a new tab in Chrome.

Bug: 404549853
Bug: 356337427
Flag: com.android.window.flags.enable_desktop_task_limit_separate_transition
Test: DesktopTasksLimiterTest, DesktopMixedTransitionHandlerTest
Change-Id: I9758ce691bb1f838b00f262b46ceede0802efd8a
parent 3113ad48
Loading
Loading
Loading
Loading
+47 −11
Original line number Diff line number Diff line
@@ -1332,6 +1332,7 @@ class DesktopTasksController(
        )
        // TODO: b/397619806 - Consolidate sharable logic with [handleFreeformTaskLaunch].
        var launchTransaction = wct
        // TODO: b/32994943 - remove dead code when cleaning up task_limit_separate_transition flag
        val taskIdToMinimize =
            addAndGetMinimizeChanges(
                deskId,
@@ -1399,6 +1400,7 @@ class DesktopTasksController(
        if (taskIdToMinimize != null) {
            addPendingMinimizeTransition(t, taskIdToMinimize, MinimizeReason.TASK_LIMIT)
        }
        addPendingTaskLimitTransition(t, deskId, launchingTaskId)
        if (launchingTaskId != null && taskRepository.isMinimizedTask(launchingTaskId)) {
            addPendingUnminimizeTransition(t, displayId, launchingTaskId, unminimizeReason)
        }
@@ -2012,11 +2014,10 @@ class DesktopTasksController(
        val expandedTasksOrderedFrontToBack = taskRepository.getExpandedTasksOrdered(displayId)
        // If we're adding a new Task we might need to minimize an old one
        // TODO(b/365725441): Handle non running task minimization
        // TODO: b/32994943 - remove dead code when cleaning up task_limit_separate_transition flag
        val taskIdToMinimize: Int? =
            if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
                desktopTasksLimiter
                    .get()
                    .getTaskIdToMinimize(expandedTasksOrderedFrontToBack, newTaskIdInFront)
            if (newTaskIdInFront != null) {
                getTaskIdToMinimize(expandedTasksOrderedFrontToBack, newTaskIdInFront)
            } else {
                null
            }
@@ -2711,12 +2712,14 @@ class DesktopTasksController(
            reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
        )
        // 2) minimize a Task if needed.
        // TODO: b/32994943 - remove dead code when cleaning up task_limit_separate_transition flag
        val taskIdToMinimize = addAndGetMinimizeChanges(deskId, wct, task.taskId)
        addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
        if (taskIdToMinimize != null) {
            addPendingMinimizeTransition(transition, taskIdToMinimize, MinimizeReason.TASK_LIMIT)
            return wct
        }
        addPendingTaskLimitTransition(transition, deskId, task.taskId)
        if (!wct.isEmpty) {
            snapEventHandler.removeTaskIfTiled(task.displayId, task.taskId)
            return wct
@@ -2756,6 +2759,8 @@ class DesktopTasksController(
                        { transition: IBinder ->
                            // The desk was already showing and we're launching a new Task - we
                            // might need to minimize another Task.
                            // TODO: b/32994943 - remove dead code when cleaning up
                            //  task_limit_separate_transition flag
                            val taskIdToMinimize =
                                addAndGetMinimizeChanges(deskId, wct, task.taskId)
                            taskIdToMinimize?.let { minimizingTaskId ->
@@ -2765,6 +2770,7 @@ class DesktopTasksController(
                                    MinimizeReason.TASK_LIMIT,
                                )
                            }
                            addPendingTaskLimitTransition(transition, deskId, task.taskId)
                            // Also track the pending launching task.
                            addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
                        }
@@ -3163,11 +3169,19 @@ class DesktopTasksController(
        newTaskId: Int?,
        launchingNewIntent: Boolean = false,
    ): Int? {
        if (!desktopTasksLimiter.isPresent) return null
        if (DesktopExperienceFlags.ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION.isTrue) return null
        val limiter = desktopTasksLimiter.getOrNull() ?: return null
        require(newTaskId == null || !launchingNewIntent)
        return desktopTasksLimiter
            .get()
            .addAndGetMinimizeTaskChanges(deskId, wct, newTaskId, launchingNewIntent)
        return limiter.addAndGetMinimizeTaskChanges(deskId, wct, newTaskId, launchingNewIntent)
    }

    private fun getTaskIdToMinimize(
        expandedTasksOrderedFrontToBack: List<Int>,
        newTaskIdInFront: Int?,
    ): Int? {
        if (DesktopExperienceFlags.ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION.isTrue) return null
        val limiter = desktopTasksLimiter.getOrNull() ?: return null
        return limiter.getTaskIdToMinimize(expandedTasksOrderedFrontToBack, newTaskIdInFront)
    }

    private fun addPendingMinimizeTransition(
@@ -3186,6 +3200,21 @@ class DesktopTasksController(
        }
    }

    private fun addPendingTaskLimitTransition(
        transition: IBinder,
        deskId: Int,
        launchTaskId: Int?,
    ) {
        if (!DesktopExperienceFlags.ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION.isTrue) return
        desktopTasksLimiter.ifPresent {
            it.addPendingTaskLimitTransition(
                transition = transition,
                deskId = deskId,
                taskId = launchTaskId,
            )
        }
    }

    private fun addPendingUnminimizeTransition(
        transition: IBinder,
        displayId: Int,
@@ -3261,6 +3290,8 @@ class DesktopTasksController(
        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            val taskIdToMinimize = bringDesktopAppsToFront(displayId, wct, newTask?.taskId)
            return { transition ->
                // TODO: b/32994943 - remove dead code when cleaning up
                //  task_limit_separate_transition flag
                taskIdToMinimize?.let { minimizingTaskId ->
                    addPendingMinimizeTransition(
                        transition = transition,
@@ -3268,6 +3299,11 @@ class DesktopTasksController(
                        minimizeReason = MinimizeReason.TASK_LIMIT,
                    )
                }
                addPendingTaskLimitTransition(
                    transition = transition,
                    deskId = deskId,
                    launchTaskId = newTask?.taskId,
                )
                if (newTask != null && addPendingLaunchTransition) {
                    addPendingAppLaunchTransition(transition, newTask.taskId, taskIdToMinimize)
                }
@@ -3281,10 +3317,9 @@ class DesktopTasksController(
        val expandedTasksOrderedFrontToBack =
            taskRepository.getExpandedTasksIdsInDeskOrdered(deskId = deskId)
        // If we're adding a new Task we might need to minimize an old one
        // TODO: b/32994943 - remove dead code when cleaning up task_limit_separate_transition flag
        val taskIdToMinimize =
            desktopTasksLimiter
                .getOrNull()
                ?.getTaskIdToMinimize(expandedTasksOrderedFrontToBack, newTaskIdInFront)
            getTaskIdToMinimize(expandedTasksOrderedFrontToBack, newTaskIdInFront)
        if (taskIdToMinimize != null) {
            val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
            // TODO(b/365725441): Handle non running task minimization
@@ -3327,6 +3362,7 @@ class DesktopTasksController(
            taskIdToMinimize?.let { minimizingTask ->
                addPendingMinimizeTransition(transition, minimizingTask, MinimizeReason.TASK_LIMIT)
            }
            addPendingTaskLimitTransition(transition, deskId, newTask?.taskId)
            deactivationRunnable?.invoke(transition)
        }
    }
+8 −0
Original line number Diff line number Diff line
@@ -124,6 +124,9 @@ class DesktopTasksLimiter(
                ?: activeTransitionTokensAndTasks[transition]
        }

        fun hasTaskLimitTransition(transition: IBinder): Boolean =
            pendingTaskLimitTransitionTokens.contains(transition)

        fun getUnminimizingTask(transition: IBinder): TaskDetails? {
            return pendingUnminimizeTransitionTokensAndTasks[transition]
                ?: activeUnminimizeTransitionTokensAndTasks[transition]
@@ -342,6 +345,11 @@ class DesktopTasksLimiter(
            LaunchDetails(deskId, taskId),
        )

    /** For testing only: returns whether there are any pending task limit transitions. */
    @VisibleForTesting
    fun hasTaskLimitTransitionForTesting(transition: IBinder) =
        minimizeTransitionObserver.hasTaskLimitTransition(transition)

    /**
     * Add a pending minimize transition change to update the list of minimized apps once the
     * transition goes through.
+242 −8
Original line number Diff line number Diff line
@@ -2132,6 +2132,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
    @DisableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
    )
    fun moveRunningTaskToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() {
        val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
@@ -2154,7 +2155,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
    )
    fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
        val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
@@ -2660,7 +2664,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
    )
    fun moveTaskToFront_bringsTasksOverLimit_multiDesksDisabled_minimizesBackTask() {
        setUpHomeTask()
        val freeformTasks =
@@ -2688,6 +2695,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun moveTaskToFront_bringsTasksOverLimit_multiDesksEnabled_minimizesBackTask() {
        val deskId = 0
        setUpHomeTask()
@@ -2712,6 +2720,34 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[1])
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun moveTaskToFront_bringsTasksOverLimit_separateTaskLimitTransition_minimizeSeparately() {
        val deskId = 0
        setUpHomeTask()
        val freeformTasks =
            (1..MAX_TASK_LIMIT + 1).map { _ ->
                setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
            }
        val transition = Binder()
        whenever(
                desktopMixedTransitionHandler.startLaunchTransition(
                    eq(TRANSIT_TO_FRONT),
                    any(),
                    eq(freeformTasks[0].taskId),
                    anyOrNull(),
                    anyOrNull(),
                )
            )
            .thenReturn(transition)

        controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)

        verify(desksOrganizer, never()).minimizeTask(any(), any(), any())
        assertThat(desktopTasksLimiter.getMinimizingTask(transition)).isNull()
        assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
    }

    @Test
    fun moveTaskToFront_minimizedTask_marksTaskAsUnminimized() {
        val transition = Binder()
@@ -2766,6 +2802,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun moveTaskToFront_bringsTasksOverLimit_remoteTransition_usesWindowLimitHandler() {
        setUpHomeTask()
        val freeformTasks = List(MAX_TASK_LIMIT + 1) { setUpFreeformTask() }
@@ -2801,7 +2838,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
    )
    fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_multiDesksDisabled_minimizesBackTask() {
        val deskId = 0
        val freeformTasks =
@@ -2829,8 +2869,40 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        wct.assertReorderAt(1, freeformTasks[0], toTop = false)
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_multiDesksDisabled_separateMinimize() {
        val deskId = 0
        val freeformTasks =
            (1..MAX_TASK_LIMIT).map { _ ->
                setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
            }
        val task = createRecentTaskInfo(1001)
        whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
        val transition = Binder()
        whenever(
                desktopMixedTransitionHandler.startLaunchTransition(
                    eq(TRANSIT_OPEN),
                    any(),
                    eq(task.taskId),
                    anyOrNull(),
                    anyOrNull(),
                )
            )
            .thenReturn(transition)

        controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)

        val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
        assertThat(wct.hierarchyOps.size).isEqualTo(1)
        wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
        assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_multiDesksEnabled_minimizesBackTask() {
        val deskId = 0
        val freeformTasks =
@@ -3225,6 +3297,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun moveToNextDisplay_toDesktopInOtherDisplay_appliesTaskLimit() {
        val transition = Binder()
        val sourceDeskId = 0
@@ -3249,6 +3322,36 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        verify(desksOrganizer).minimizeTask(wct, targetDeskId, targetDeskTasks[0])
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
    )
    fun moveToNextDisplay_toDesktopInOtherDisplay_appliesTaskLimitSeparate() {
        val transition = Binder()
        val sourceDeskId = 0
        val targetDeskId = 2
        taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
        taskRepository.setDeskInactive(deskId = targetDeskId)
        val targetDeskTasks =
            (1..MAX_TASK_LIMIT + 1).map { _ ->
                setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = targetDeskId)
            }
        // Set up two display ids
        whenever(rootTaskDisplayAreaOrganizer.displayIds)
            .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
        whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
            .thenReturn(transition)
        val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)

        controller.moveToNextDisplay(task.taskId)

        val wct = getLatestTransition()
        assertNotNull(wct)
        assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
        verify(desksOrganizer, never()).minimizeTask(wct, targetDeskId, targetDeskTasks[0])
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
@@ -4106,7 +4209,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
    )
    fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_multiDesksDisabled_otherTaskIsMinimized() {
        val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
        freeformTasks.forEach { markTaskVisible(it) }
@@ -4120,8 +4226,26 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        wct.assertReorderAt(1, freeformTasks[0], toTop = false)
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_multiDesksDisabled_separateMinimize() {
        val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
        freeformTasks.forEach { markTaskVisible(it) }
        val fullscreenTask = createFullscreenTask()
        val transition = Binder()

        val wct = controller.handleRequest(transition, createTransition(fullscreenTask))

        // Make sure we reorder the new task to top, and the back task to the bottom
        assertThat(wct!!.hierarchyOps.size).isEqualTo(1)
        wct.assertReorderAt(0, fullscreenTask, toTop = true)
        assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_multiDesksEnabled_otherTaskIsMinimized() {
        val deskId = 5
        taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
@@ -4147,7 +4271,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
    )
    fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_multiDesksDisabled_otherTaskIsMinimized() {
        val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
        freeformTasks.forEach { markTaskVisible(it) }
@@ -4170,6 +4297,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_multiDesksEnabled_otherTaskIsMinimized() {
        val deskId = 5
        taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
@@ -4192,7 +4320,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_separateMinimizeFlagEnabled_minimizeSeparately() {
        val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
        freeformTasks.forEach { markTaskVisible(it) }
        val fullscreenTask = createFullscreenTask()
        val transition = Binder()

        val wct = controller.handleRequest(transition, createTransition(fullscreenTask))

        assertThat(wct!!.hierarchyOps.size).isAtMost(1)
        assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
    }

    @Test
    @DisableFlags(
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
    )
    fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_multiDesksDisabled_existingAndNewTasksAreMinimized() {
        val minimizedTask = setUpFreeformTask()
        taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId)
@@ -4218,8 +4363,33 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        }
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_separateMinFlagEnabled_minimizeSeparately() {
        val minimizedTask = setUpFreeformTask()
        taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId)
        val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
        freeformTasks.forEach { markTaskVisible(it) }
        val homeTask = setUpHomeTask()
        val fullscreenTask = createFullscreenTask()
        fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)

        val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))

        assertThat(wct!!.hierarchyOps.size).isEqualTo(10)
        wct.assertReorderAt(0, fullscreenTask, toTop = true)
        // Make sure we reorder the home task to the top, desktop tasks to top of them and minimized
        // task is under the home task.
        wct.assertReorderAt(1, homeTask, toTop = true)
        // Oldest task that needs to minimized is never reordered to top over Home.
        val taskToMinimize = freeformTasks[0]
        wct.assertReorder(taskToMinimize.token, toTop = true)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_multiDesksEnabled_existingAndNewTasksAreMinimized() {
        // A desk with a minimized tasks, and non-minimized tasks already at the task limit.
        val deskId = 5
@@ -4427,7 +4597,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
    )
    fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_multiDesksDisabled_minimize() {
        val deskId = 0
        val freeformTasks =
@@ -4446,6 +4619,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_multiDesksEnabled_minimize() {
        val deskId = 0
        val freeformTasks =
@@ -4462,6 +4636,26 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0])
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun handleRequest_freeform_aboveTaskLimit_separateMinimizeFlagEnabled_minimizeSeparately() {
        val deskId = 0
        val freeformTasks =
            (1..MAX_TASK_LIMIT).map { _ ->
                setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
            }
        freeformTasks.forEach { markTaskVisible(it) }
        val newFreeformTask = createFreeformTask()
        val transition = Binder()

        val wct =
            controller.handleRequest(transition, createTransition(newFreeformTask, TRANSIT_OPEN))

        assertNotNull(wct)
        assertThat(wct.hierarchyOps).isEmpty()
        assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun handleRequest_freeformTaskFromInactiveDesk_tracksDeskDeactivation() {
@@ -6848,7 +7042,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @DisableFlags(
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
    )
    fun openInstance_fromFreeform_multiDesksDisabled_minimizesIfNeeded() {
        setUpLandscapeDisplay()
        val deskId = 0
@@ -6885,6 +7082,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
    )
    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
    fun openInstance_fromFreeform_multiDesksEnabled_minimizesIfNeeded() {
        setUpLandscapeDisplay()
        val deskId = 0
@@ -6914,6 +7112,42 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        verify(desksOrganizer).minimizeTask(wct, deskId, oldestTask)
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
    )
    fun openInstance_fromFreeform_minimizesSeparately() {
        setUpLandscapeDisplay()
        val deskId = 0
        val freeformTasks =
            (1..MAX_TASK_LIMIT + 1).map { _ ->
                setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
            }
        val oldestTask = freeformTasks.first()
        val newestTask = freeformTasks.last()

        val transition = Binder()
        val wctCaptor = argumentCaptor<WindowContainerTransaction>()
        whenever(
                desktopMixedTransitionHandler.startLaunchTransition(
                    anyInt(),
                    wctCaptor.capture(),
                    anyInt(),
                    anyOrNull(),
                    anyOrNull(),
                )
            )
            .thenReturn(transition)

        runOpenInstance(newestTask, freeformTasks[1].taskId)

        val wct = wctCaptor.firstValue
        verify(desksOrganizer, never()).minimizeTask(wct, deskId, oldestTask)
        assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
    fun openInstance_fromFreeform_exitsImmersiveIfNeeded() {