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

Commit 2d8f7975 authored by Jorge Gil's avatar Jorge Gil Committed by Android (Google) Code Review
Browse files

Merge "Desks: Handle back-nav transitions correctly" into main

parents afa16744 7afc6b31
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1382,6 +1382,7 @@ public abstract class WMShellModule {
            Optional<DesktopUserRepositories> desktopUserRepositories,
            Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
            Optional<BackAnimationController> backAnimationController,
            DesksOrganizer desksOrganizer,
            DesktopState desktopState,
            ShellInit shellInit) {
        return desktopUserRepositories.flatMap(
@@ -1391,6 +1392,7 @@ public abstract class WMShellModule {
                                        repository,
                                        desktopMixedTransitionHandler.get(),
                                        backAnimationController.get(),
                                        desksOrganizer,
                                        desktopState,
                                        shellInit)));
    }
+38 −13
Original line number Diff line number Diff line
@@ -16,8 +16,7 @@

package com.android.wm.shell.desktopmode

import android.app.ActivityManager
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.ActivityManager.RunningTaskInfo
import android.os.IBinder
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_TO_BACK
@@ -27,6 +26,7 @@ import android.window.TransitionInfo
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.desktopmode.DesktopState
@@ -41,6 +41,7 @@ class DesktopBackNavTransitionObserver(
    private val desktopUserRepositories: DesktopUserRepositories,
    private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
    private val backAnimationController: BackAnimationController,
    private val desksOrganizer: DesksOrganizer,
    desktopState: DesktopState,
    shellInit: ShellInit,
) {
@@ -51,7 +52,7 @@ class DesktopBackNavTransitionObserver(
    }

    fun onInit() {
        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopBackNavigationTransitionObserver: onInit")
        logD("onInit")
    }

    fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
@@ -73,10 +74,7 @@ class DesktopBackNavTransitionObserver(
            if (taskInfo == null || taskInfo.taskId == -1) continue

            val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
            if (
                desktopRepository.isActiveTask(taskInfo.taskId) &&
                    taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
            ) {
            if (desktopRepository.isExitingDesktopTask(change)) {
                desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId)
            }
        }
@@ -96,7 +94,7 @@ class DesktopBackNavTransitionObserver(
                if (
                    isInDesktop &&
                        change.mode == TRANSIT_TO_BACK &&
                        taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
                        desktopRepository.isDesktopTask(taskInfo)
                ) {
                    val isLastTask =
                        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
@@ -104,6 +102,10 @@ class DesktopBackNavTransitionObserver(
                        } else {
                            desktopRepository.isOnlyVisibleTask(taskInfo.taskId, taskInfo.displayId)
                        }
                    logD(
                        "handleBackNavigation marking to-back taskId=%d as minimized",
                        taskInfo.taskId,
                    )
                    desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
                    desktopMixedTransitionHandler.addPendingMixedTransition(
                        DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
@@ -137,6 +139,7 @@ class DesktopBackNavTransitionObserver(

            if (minimizingTask == null) return
            // If the transition has wallpaper closing, it means we are moving out of desktop.
            logD("handleBackNavigation marking close taskId=%d as minimized", minimizingTask)
            desktopMixedTransitionHandler.addPendingMixedTransition(
                DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
                    transition,
@@ -151,7 +154,7 @@ class DesktopBackNavTransitionObserver(
     * Given this a closing task in a closing transition, a task is assumed to be closed by back
     * navigation if:
     * 1) Desktop mode is visible.
     * 2) Task is in freeform.
     * 2) It is a desktop task.
     * 3) Task is the latest task that the back gesture is triggered on.
     * 4) It's not marked as a closing task as a result of closing it by the app header.
     *
@@ -159,14 +162,12 @@ class DesktopBackNavTransitionObserver(
     * will be rare. E.g. triggering back navigation on an app that pops up a close dialog, and
     * closing it will minimize it here.
     */
    private fun getMinimizingTaskForClosingTransition(
        taskInfo: ActivityManager.RunningTaskInfo
    ): Int? {
    private fun getMinimizingTaskForClosingTransition(taskInfo: RunningTaskInfo): Int? {
        val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
        val isInDesktop = desktopRepository.isAnyDeskActive(taskInfo.displayId)
        if (
            isInDesktop &&
                taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
                desktopRepository.isDesktopTask(taskInfo) &&
                backAnimationController.latestTriggerBackTask == taskInfo.taskId &&
                !desktopRepository.isClosingTask(taskInfo.taskId)
        ) {
@@ -175,4 +176,28 @@ class DesktopBackNavTransitionObserver(
        }
        return null
    }

    private fun DesktopRepository.isDesktopTask(task: RunningTaskInfo): Boolean =
        if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            isActiveTask(task.taskId)
        } else {
            task.isFreeform
        }

    private fun DesktopRepository.isExitingDesktopTask(change: TransitionInfo.Change): Boolean {
        val task = change.taskInfo ?: return false
        return if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            isActiveTask(task.taskId) && desksOrganizer.getDeskAtEnd(change) == null
        } else {
            isActiveTask(task.taskId) && !task.isFreeform
        }
    }

    private fun logD(msg: String, vararg arguments: Any?) {
        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
    }

    companion object {
        private const val TAG = "DesktopBackNavTransitionObserver"
    }
}
+31 −14
Original line number Diff line number Diff line
@@ -60,7 +60,9 @@ import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.view.WindowManager.transitTypeToString
import android.widget.Toast
import android.window.DesktopExperienceFlags
import android.window.DesktopExperienceFlags.DesktopExperienceFlag
@@ -1516,7 +1518,7 @@ class DesktopTasksController(
    ): IBinder {
        logV(
            "startLaunchTransition type=%s launchingTaskId=%d deskId=%d displayId=%d",
            WindowManager.transitTypeToString(transitionType),
            transitTypeToString(transitionType),
            launchingTaskId,
            deskId,
            displayId,
@@ -2894,7 +2896,7 @@ class DesktopTasksController(
        )
        val wct = WindowContainerTransaction()
        if (!anyDeskActive && !shouldEnterDesktop) {
            // We are outside of desktop mode and already existing desktop task is being
            // We are outside of desktop mode and an already existing desktop task is being
            // launched. We should make this task go to fullscreen instead of freeform. Note
            // that this means any re-launch of a freeform window outside of desktop will be in
            // fullscreen as long as default-desktop flag is disabled.
@@ -2902,12 +2904,7 @@ class DesktopTasksController(
                addMoveToFullscreenChanges(
                    wct = wct,
                    taskInfo = task,
                    willExitDesktop =
                        willExitDesktop(
                            triggerTaskId = task.taskId,
                            displayId = task.displayId,
                            forceExitDesktop = true,
                        ),
                    willExitDesktop = false, // Already outside desktop.
                )
            runOnTransitStart?.invoke(transition)
            return wct
@@ -3174,23 +3171,43 @@ class DesktopTasksController(
    }

    /**
     * Handle task closing by removing wallpaper activity if it's the last active task.
     *
     * TODO: b/394268248 - desk needs to be deactivated.
     * Handles a closing task. This usually means deactivating and cleaning up the desk if it was
     * the last task in it. It also handles to-back transitions of the last desktop task as a
     * minimize operation.
     */
    private fun handleTaskClosing(
        task: RunningTaskInfo,
        transition: IBinder,
        requestType: Int,
        @WindowManager.TransitionType requestType: Int,
    ): WindowContainerTransaction? {
        logV("handleTaskClosing")
        logV(
            "handleTaskClosing taskId=%d closingType=%s",
            task.taskId,
            transitTypeToString(requestType),
        )
        if (!isAnyDeskActive(task.displayId)) return null
        val deskId = taskRepository.getDeskIdForTask(task.taskId)
        if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            return null
        }

        val wct = WindowContainerTransaction()
        if (
            DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue &&
                requestType == TRANSIT_TO_BACK
        ) {
            val isLastTask = taskRepository.isOnlyVisibleTask(task.taskId, task.displayId)
            logV(
                "Handling to-back of taskId=%d (isLast=%b) as minimize in deskId=%d",
                task.taskId,
                isLastTask,
                deskId,
            )
            desksOrganizer.minimizeTask(
                wct = wct,
                deskId = checkNotNull(deskId) { "Expected non-null deskId" },
                task = task,
            )
        }
        val deactivationRunnable =
            performDesktopExitCleanupIfNeeded(
                taskId = task.taskId,
+98 −10
Original line number Diff line number Diff line
@@ -32,17 +32,14 @@ import android.window.IWindowContainerToken
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import com.android.window.flags.Flags
import com.android.wm.shell.MockToken
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
import com.android.wm.shell.shared.desktopmode.FakeDesktopState
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
@@ -62,12 +59,11 @@ import org.mockito.kotlin.whenever
class DesktopBackNavTransitionObserverTest : ShellTestCase() {

    private val testExecutor = mock<ShellExecutor>()
    private val transitions = mock<Transitions>()
    private val userRepositories = mock<DesktopUserRepositories>()
    private val taskRepository = mock<DesktopRepository>()
    private val mixedHandler = mock<DesktopMixedTransitionHandler>()
    private val backAnimationController = mock<BackAnimationController>()
    private val wallpaperToken = MockToken().token()
    private val desksOrganizer = mock<DesksOrganizer>()
    private val desktopState = FakeDesktopState()

    private lateinit var transitionObserver: DesktopBackNavTransitionObserver
@@ -86,6 +82,7 @@ class DesktopBackNavTransitionObserverTest : ShellTestCase() {
                userRepositories,
                mixedHandler,
                backAnimationController,
                desksOrganizer,
                desktopState,
                shellInit,
            )
@@ -96,6 +93,26 @@ class DesktopBackNavTransitionObserverTest : ShellTestCase() {
    fun backNavigation_taskMinimized() {
        val task = createTaskInfo(1)
        whenever(taskRepository.isAnyDeskActive(any())).thenReturn(true)
        whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true)

        transitionObserver.onTransitionReady(
            transition = mock(),
            info = createBackNavigationTransition(task),
        )

        verify(taskRepository).minimizeTask(task.displayId, task.taskId)
        verify(mixedHandler).addPendingMixedTransition(any())
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
    )
    fun backNavigation_nonFreeformDesktopTask_taskMinimized() {
        val task = createTaskInfo(1, windowingMode = WINDOWING_MODE_FULLSCREEN)
        whenever(taskRepository.isAnyDeskActive(any())).thenReturn(true)
        whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true)

        transitionObserver.onTransitionReady(
            transition = mock(),
@@ -112,6 +129,7 @@ class DesktopBackNavTransitionObserverTest : ShellTestCase() {
        val task = createTaskInfo(1)
        val transition = mock<IBinder>()
        whenever(taskRepository.isAnyDeskActive(any())).thenReturn(true)
        whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true)
        whenever(taskRepository.isOnlyVisibleTask(task.taskId, task.displayId)).thenReturn(false)
        whenever(taskRepository.hasOnlyOneVisibleTask(task.displayId)).thenReturn(false)
        whenever(taskRepository.isClosingTask(task.taskId)).thenReturn(false)
@@ -139,6 +157,7 @@ class DesktopBackNavTransitionObserverTest : ShellTestCase() {
        val task = createTaskInfo(1)
        val transition = mock<IBinder>()
        whenever(taskRepository.isAnyDeskActive(any())).thenReturn(true)
        whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true)
        whenever(taskRepository.isClosingTask(task.taskId)).thenReturn(false)
        whenever(backAnimationController.latestTriggerBackTask).thenReturn(task.taskId)

@@ -166,6 +185,7 @@ class DesktopBackNavTransitionObserverTest : ShellTestCase() {
        val task = createTaskInfo(1)
        val transition = mock<IBinder>()
        whenever(taskRepository.isAnyDeskActive(any())).thenReturn(true)
        whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true)
        whenever(taskRepository.isClosingTask(task.taskId)).thenReturn(false)
        whenever(backAnimationController.latestTriggerBackTask).thenReturn(task.taskId)

@@ -228,6 +248,7 @@ class DesktopBackNavTransitionObserverTest : ShellTestCase() {

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun removeTasks_onTaskFullscreenLaunchWithOpenTransition_taskRemovedFromRepo() {
        val task = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
        whenever(taskRepository.isAnyDeskActive(any())).thenReturn(true)
@@ -242,8 +263,45 @@ class DesktopBackNavTransitionObserverTest : ShellTestCase() {
        verify(taskRepository).removeTask(task.displayId, task.taskId)
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
    )
    fun removeTasks_onTaskFullscreenInDeskLaunchWithOpenTransition_taskNotRemovedFromRepo() {
        val deskId = 0
        val task = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
        val transitionInfo = createOpenChangeTransition(task)
        whenever(taskRepository.isAnyDeskActive(any())).thenReturn(true)
        whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true)
        whenever(desksOrganizer.getDeskAtEnd(transitionInfo.changes.first())).thenReturn(deskId)

        transitionObserver.onTransitionReady(transition = mock(), info = transitionInfo)

        verify(taskRepository, never()).removeTask(task.displayId, task.taskId)
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
    )
    fun removeTasks_onTaskOutsideDeskLaunchWithOpenTransition_taskRemovedFromRepo() {
        val task = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
        val transitionInfo = createOpenChangeTransition(task)
        whenever(taskRepository.isAnyDeskActive(any())).thenReturn(true)
        whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true)
        whenever(desksOrganizer.getDeskAtEnd(transitionInfo.changes.first())).thenReturn(null)

        transitionObserver.onTransitionReady(transition = mock(), info = transitionInfo)

        verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId)
        verify(taskRepository).removeTask(task.displayId, task.taskId)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun removeTasks_onTaskFullscreenLaunchExitDesktopTransition_taskRemovedFromRepo() {
        val task = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
        whenever(taskRepository.isAnyDeskActive(any())).thenReturn(true)
@@ -258,10 +316,40 @@ class DesktopBackNavTransitionObserverTest : ShellTestCase() {
        verify(taskRepository).removeTask(task.displayId, task.taskId)
    }

    private fun WindowContainerTransaction.assertIndexInBounds(index: Int) {
        assertWithMessage("WCT does not have a hierarchy operation at index $index")
            .that(hierarchyOps.size)
            .isGreaterThan(index)
    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
    )
    fun removeTasks_onTaskFullscreenInDeskLaunchExitDesktopTransition_taskNotRemovedFromRepo() {
        val deskId = 0
        val task = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
        val transitionInfo = createOpenChangeTransition(task, TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG)
        whenever(taskRepository.isAnyDeskActive(any())).thenReturn(true)
        whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true)
        whenever(desksOrganizer.getDeskAtEnd(transitionInfo.changes.first())).thenReturn(deskId)

        transitionObserver.onTransitionReady(transition = mock(), info = transitionInfo)

        verify(taskRepository, never()).removeTask(task.displayId, task.taskId)
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
    )
    fun removeTasks_onTaskOutsideDeskLaunchExitDesktopTransition_taskRemovedFromRepo() {
        val task = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)
        val transitionInfo = createOpenChangeTransition(task, TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG)
        whenever(taskRepository.isAnyDeskActive(any())).thenReturn(true)
        whenever(taskRepository.isActiveTask(task.taskId)).thenReturn(true)
        whenever(desksOrganizer.getDeskAtEnd(transitionInfo.changes.first())).thenReturn(null)

        transitionObserver.onTransitionReady(transition = mock(), info = transitionInfo)

        verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId)
        verify(taskRepository).removeTask(task.displayId, task.taskId)
    }

    private fun createOpenChangeTransition(
+71 −22
Original line number Diff line number Diff line
@@ -4973,20 +4973,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun handleRequest_freeformTaskFromInactiveDesk_tracksDeskDeactivation() {
        val deskId = 0
        val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
        taskRepository.setDeskInactive(deskId = deskId)

        val transition = Binder()
        controller.handleRequest(transition, createTransition(freeformTask))

        verify(desksTransitionsObserver)
            .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
    }

    @Test
    fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() {
        taskRepository.setDeskInactive(deskId = 0)
@@ -5982,20 +5968,83 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
    fun handleRequest_closeTransition_minimizadTask_withWallpaper_removesWallpaper() {
        val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
        val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
    )
    fun handleRequest_toBackTransition_noActiveDesk_notHandled() {
        taskRepository.setDeskInactive(deskId = 0)
        val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)

        taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
        // Task is being minimized so mark it as not visible.
        taskRepository.updateTask(displayId = DEFAULT_DISPLAY, task2.taskId, isVisible = false)
        val result =
            controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
            controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))

        assertNull(result, "Should not handle request")
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
    )
    fun handleRequest_toBackTransition_minimizesTask() {
        taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
        val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)

        val result =
            controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))

        assertNotNull(result) { "Should handle request" }
        verify(desksOrganizer).minimizeTask(result, deskId = 0, task)
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
    )
    fun handleRequest_toBackTransition_lastTask_deactivatesDesk() {
        taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
        val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)

        val transition = Binder()
        val result =
            controller.handleRequest(transition, createTransition(task, type = TRANSIT_TO_BACK))

        assertNotNull(result) { "Should handle request" }
        verify(desksOrganizer).deactivateDesk(result, deskId = 0)
        verify(desksTransitionsObserver)
            .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0))
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
        Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
    )
    fun handleRequest_toBackTransition_notLastTask_doesNotDeactivateDesk() {
        taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
        setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
        val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)

        val transition = Binder()
        controller.handleRequest(transition, createTransition(task, type = TRANSIT_TO_BACK))

        verify(desksOrganizer, never()).deactivateDesk(any(), deskId = eq(0))
        verify(desksTransitionsObserver, never())
            .addPendingTransition(
                argThat {
                    this is DeskTransition.RemoveDesk &&
                        this.token == transition &&
                        this.deskId == 0
                }
            )
    }

    @Test
    fun handleRequest_freeformTask_displayDoesntHandleDesktop_returnNull() {
        desktopState.overrideDesktopModeSupportPerDisplay[SECOND_DISPLAY] = false