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

Commit c6e124f4 authored by Jorge Gil's avatar Jorge Gil
Browse files

[15/N] Desks: Deactivate desk when last window is minimized

When the last task is minimized, the desk is now deactivated to prevent
future app launches from launching in freeform.

A small refactor is made to move the deactivation and transition
tracking to a utility method.

Note: minimization is not yet migrated to multi-desks, so while the desk
is now deactivated, the minimized task remains visible because it is
only being sent to the back of its desk container, which effectively
keeps it in place since there's no home/wallpaper inside the desk.

Flag: com.android.window.flags.enable_multiple_desktops_backend
Bug: 391485148
Bug: 394268248
Test: open fullscreen app, enter desktop, minimize it
 1) DesktopRepository dump shows activeDeskId=null
 2) Home launches
Change-Id: I25eb436595732cf6831f2a64f067cd771e337153
parent 5dc10c1f
Loading
Loading
Loading
Loading
+76 −46
Original line number Diff line number Diff line
@@ -153,6 +153,16 @@ import java.util.concurrent.TimeUnit
import java.util.function.Consumer
import kotlin.jvm.optionals.getOrNull

/**
 * A callback to be invoked when a transition is started via |Transitions.startTransition| with the
 * transition binder token that it produces.
 *
 * Useful when multiple components are appending WCT operations to a single transition that is
 * started outside of their control, and each of them wants to track the transition lifecycle
 * independently by cross-referencing the transition token with future ready-transitions.
 */
typealias RunOnTransitStart = (IBinder) -> Unit

/** Handles moving tasks in and out of desktop */
class DesktopTasksController(
    private val context: Context,
@@ -792,6 +802,7 @@ class DesktopTasksController(
        taskInfo: RunningTaskInfo,
    ): ((IBinder) -> Unit) {
        val taskId = taskInfo.taskId
        val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
        snapEventHandler.removeTaskIfTiled(displayId, taskId)
        val shouldExitDesktop =
            willExitDesktop(
@@ -800,28 +811,14 @@ class DesktopTasksController(
                forceToFullscreen = false,
            )
        taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true)
        // TODO: b/393978539 - Deactivation should not happen in desktop-first devices.
        val deactivatingDeskId =
            if (shouldExitDesktop) {
                performDesktopExitCleanUp(wct, displayId, shouldEndUpAtHome = true)
                val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
                if (
                    DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue && deskId != null
                ) {
                    desksOrganizer.deactivateDesk(wct, deskId)
                }
                deskId
            } else {
                null
            }
        val deskDeactivationRunnable =
            deactivatingDeskId?.let { deskId ->
                { transition: IBinder ->
                    desksTransitionObserver.addPendingTransition(
                        DeskTransition.DeactivateDesk(token = transition, deskId = deskId)
        val desktopExitRunnable =
            performDesktopExitCleanUp(
                wct = wct,
                deskId = deskId,
                displayId = displayId,
                willExitDesktop = shouldExitDesktop,
                shouldEndUpAtHome = true,
            )
                }
            }

        taskRepository.addClosingTask(displayId, taskId)
        taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
@@ -839,7 +836,7 @@ class DesktopTasksController(
                ?.runOnTransitionStart
        return { transitionToken ->
            immersiveRunnable?.invoke(transitionToken)
            deskDeactivationRunnable?.invoke(transitionToken)
            desktopExitRunnable?.invoke(transitionToken)
        }
    }

@@ -874,12 +871,20 @@ class DesktopTasksController(

    private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
        val taskId = taskInfo.taskId
        val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
        val displayId = taskInfo.displayId
        val wct = WindowContainerTransaction()

        snapEventHandler.removeTaskIfTiled(displayId, taskId)
        // TODO: b/394268248 - desk needs to be deactivated when minimizing the last task and going
        //  home.
        performDesktopExitCleanupIfNeeded(taskId, displayId, wct, forceToFullscreen = false)
        taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true)
        val willExitDesktop = willExitDesktop(taskId, displayId, forceToFullscreen = false)
        val desktopExitRunnable =
            performDesktopExitCleanUp(
                wct = wct,
                deskId = deskId,
                displayId = displayId,
                willExitDesktop = willExitDesktop,
            )
        // Notify immersive handler as it might need to exit immersive state.
        val exitResult =
            desktopImmersiveController.exitImmersiveIfApplicable(
@@ -901,6 +906,7 @@ class DesktopTasksController(
            )
        }
        exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
        desktopExitRunnable?.invoke(transition)
    }

    /** Move a task with given `taskId` to fullscreen */
@@ -949,7 +955,7 @@ class DesktopTasksController(
        logV("moveToFullscreenWithAnimation taskId=%d", task.taskId)
        val wct = WindowContainerTransaction()
        val willExitDesktop = willExitDesktop(task.taskId, task.displayId, forceToFullscreen = true)
        val deactivatingDeskId = addMoveToFullscreenChanges(wct, task, willExitDesktop)
        val deactivationRunnable = addMoveToFullscreenChanges(wct, task, willExitDesktop)

        // We are moving a freeform task to fullscreen, put the home task under the fullscreen task.
        if (!forceEnterDesktop(task.displayId)) {
@@ -964,11 +970,7 @@ class DesktopTasksController(
                position,
                mOnAnimationFinishedCallback,
            )
        if (deactivatingDeskId != null) {
            desksTransitionObserver.addPendingTransition(
                DeskTransition.DeactivateDesk(token = transition, deskId = deactivatingDeskId)
            )
        }
        deactivationRunnable?.invoke(transition)

        // handles case where we are moving to full screen without closing all DW tasks.
        if (!taskRepository.isOnlyVisibleNonClosingTask(task.taskId)) {
@@ -1804,19 +1806,30 @@ class DesktopTasksController(
        wct: WindowContainerTransaction,
        forceToFullscreen: Boolean,
        shouldEndUpAtHome: Boolean = true,
    ) {
    ): RunOnTransitStart? {
        taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = !forceToFullscreen)
        if (!willExitDesktop(taskId, displayId, forceToFullscreen)) {
            return
            return null
        }
        performDesktopExitCleanUp(wct, displayId, shouldEndUpAtHome)
        // TODO: b/394268248 - update remaining callers to pass in a |deskId| and apply the
        //  |RunOnTransitStart| when the transition is started.
        return performDesktopExitCleanUp(
            wct = wct,
            deskId = null,
            displayId = displayId,
            willExitDesktop = true,
            shouldEndUpAtHome = shouldEndUpAtHome,
        )
    }

    private fun performDesktopExitCleanUp(
        wct: WindowContainerTransaction,
        deskId: Int?,
        displayId: Int,
        willExitDesktop: Boolean,
        shouldEndUpAtHome: Boolean = true,
    ) {
    ): RunOnTransitStart? {
        if (!willExitDesktop) return null
        desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
            FULLSCREEN_ANIMATION_DURATION
        )
@@ -1826,6 +1839,7 @@ class DesktopTasksController(
            // intent.
            addLaunchHomePendingIntent(wct, displayId)
        }
        return prepareDeskDeactivationIfNeeded(wct, deskId)
    }

    fun releaseVisualIndicator() {
@@ -2507,7 +2521,7 @@ class DesktopTasksController(
        wct: WindowContainerTransaction,
        taskInfo: RunningTaskInfo,
        willExitDesktop: Boolean,
    ): Int? {
    ): RunOnTransitStart? {
        val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
        val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
        val targetWindowingMode =
@@ -2526,15 +2540,14 @@ class DesktopTasksController(
            wct.reparent(taskInfo.token, tdaInfo.token, /* onTop= */ true)
        }
        taskRepository.setPipShouldKeepDesktopActive(taskInfo.displayId, keepActive = false)
        if (willExitDesktop) {
            performDesktopExitCleanUp(wct, taskInfo.displayId, shouldEndUpAtHome = false)
        val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
            if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue && deskId != null) {
                desksOrganizer.deactivateDesk(wct, deskId)
                return deskId
            }
        }
        return null
        return performDesktopExitCleanUp(
            wct = wct,
            deskId = deskId,
            displayId = taskInfo.displayId,
            willExitDesktop = willExitDesktop,
            shouldEndUpAtHome = false,
        )
    }

    private fun cascadeWindow(bounds: Rect, displayLayout: DisplayLayout, displayId: Int) {
@@ -2695,6 +2708,23 @@ class DesktopTasksController(
        )
    }

    /**
     * TODO: b/393978539 - Deactivation should not happen in desktop-first devices when going home.
     */
    private fun prepareDeskDeactivationIfNeeded(
        wct: WindowContainerTransaction,
        deskId: Int?,
    ): RunOnTransitStart? {
        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return null
        if (deskId == null) return null
        desksOrganizer.deactivateDesk(wct, deskId)
        return { transition ->
            desksTransitionObserver.addPendingTransition(
                DeskTransition.DeactivateDesk(token = transition, deskId = deskId)
            )
        }
    }

    /** Removes the default desk in the given display. */
    @Deprecated("Deprecated with multi-desks.", ReplaceWith("removeDesk()"))
    fun removeDefaultDeskInDisplay(displayId: Int) {
+10 −1
Original line number Diff line number Diff line
@@ -92,10 +92,11 @@ class DesksTransitionObserver(
                }
            }
            is DeskTransition.DeactivateDesk -> {
                var visibleDeactivation = false
                for (change in info.changes) {
                    val isDeskChange = desksOrganizer.isDeskChange(change, deskTransition.deskId)
                    if (isDeskChange) {
                        desktopRepository.setDeskInactive(deskId = deskTransition.deskId)
                        visibleDeactivation = true
                        continue
                    }
                    val taskId = change.taskInfo?.taskId ?: continue
@@ -109,6 +110,14 @@ class DesksTransitionObserver(
                        )
                    }
                }
                // Always deactivate even if there's no change that confirms the desk was
                // deactivated. Some interactions, such as the desk deactivating because it's
                // occluded by a fullscreen task result in a transition change, but others, such
                // as transitioning from an empty desk to home may not.
                if (!visibleDeactivation) {
                    logD("Deactivating desk without transition change")
                }
                desktopRepository.setDeskInactive(deskId = deskTransition.deskId)
            }
        }
    }
+42 −0
Original line number Diff line number Diff line
@@ -2970,6 +2970,48 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        }
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun onDesktopWindowMinimize_lastWindow_deactivatesDesk() {
        val task = setUpFreeformTask()
        val transition = Binder()
        whenever(
                freeformTaskTransitionStarter.startMinimizedModeTransition(
                    any(),
                    anyInt(),
                    anyBoolean(),
                )
            )
            .thenReturn(transition)

        controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)

        val captor = argumentCaptor<WindowContainerTransaction>()
        verify(freeformTaskTransitionStarter)
            .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true))
        verify(desksOrganizer).deactivateDesk(captor.firstValue, deskId = 0)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun onDesktopWindowMinimize_lastWindow_addsPendingDeactivateTransition() {
        val task = setUpFreeformTask()
        val transition = Binder()
        whenever(
                freeformTaskTransitionStarter.startMinimizedModeTransition(
                    any(),
                    anyInt(),
                    anyBoolean(),
                )
            )
            .thenReturn(transition)

        controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)

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

    @Test
    fun onPipTaskMinimize_autoEnterEnabled_startPipTransition() {
        val task = setUpPipTask(autoEnterEnabled = true)
+17 −0
Original line number Diff line number Diff line
@@ -227,4 +227,21 @@ class DesksTransitionObserverTest : ShellTestCase() {

        assertThat(repository.isActiveTaskInDesk(deskId = 5, taskId = exitingTask.taskId)).isFalse()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun onTransitionReady_deactivateDeskWithoutVisibleChange_updatesRepository() {
        val transition = Binder()
        val deactivateTransition = DeskTransition.DeactivateDesk(transition, deskId = 5)
        repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
        repository.setActiveDesk(DEFAULT_DISPLAY, deskId = 5)

        observer.addPendingTransition(deactivateTransition)
        observer.onTransitionReady(
            transition = transition,
            info = TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0),
        )

        assertThat(repository.getActiveDeskId(DEFAULT_DISPLAY)).isNull()
    }
}