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

Commit 71d5260b authored by Jorge Gil's avatar Jorge Gil
Browse files

[13/N] Desks: Implement move to fullscreen from a desk

Updates the move-to-fullscreen interactions (App Header button, app
handle, etc), to properly move a desk's task out of the desk and into
fullscreen. This includes reparenting the task to the default TDA,
tracking the transition and updating the repository state when the
transition is ready to remove the task from the desk and deactivate the
desk.

Also wires the adb command to move a task out of desktop to this new
implementation.

Flag: com.android.window.flags.enable_multiple_desktops_backend
Bug: 393962404
Bug: 393977227
Bug: 394268248
Test: drag task to desktop, then drag back to fullscreen

Change-Id: I9335b8cf796f1ee6062c9c1f50b775bade2c0abf
parent 7c7e6700
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -204,8 +204,8 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
                pw.println("Error: task id should be an integer")
                return false
            }
        pw.println("Not implemented.")
        return false
        controller.moveToFullscreen(taskId, transitionSource = UNKNOWN)
        return true
    }

    private fun runCanCreateDesk(args: Array<String>, pw: PrintWriter): Boolean {
+179 −74
Original line number Diff line number Diff line
@@ -227,10 +227,19 @@ class DesktopRepository(
        desktopData.setActiveDesk(displayId = displayId, deskId = deskId)
    }

    /** Sets the given desk as inactive if it was active. */
    fun setDeskInactive(deskId: Int) {
        desktopData.setDeskInactive(deskId)
    }

    /** Returns the id of the active desk in the given display, if any. */
    @VisibleForTesting
    fun getActiveDeskId(displayId: Int): Int? = desktopData.getActiveDesk(displayId)?.deskId

    /** Returns the id of the desk to which this task belongs. */
    fun getDeskIdForTask(taskId: Int): Int? =
        desktopData.desksSequence().find { desk -> desk.activeTasks.contains(taskId) }?.deskId

    /**
     * Adds task with [taskId] to the list of freeform tasks on [displayId]'s active desk.
     *
@@ -271,20 +280,40 @@ class DesktopRepository(
    @VisibleForTesting
    fun removeActiveTask(taskId: Int, excludedDeskId: Int? = null) {
        val affectedDisplays = mutableSetOf<Int>()
        desktopData.forAllDesks { displayId, desk ->
            if (desk.deskId != excludedDeskId && desk.activeTasks.remove(taskId)) {
        desktopData
            .desksSequence()
            .filter { desk -> desk.displayId != excludedDeskId }
            .forEach { desk ->
                val removed = removeActiveTaskFromDesk(desk.deskId, taskId, notifyListeners = false)
                if (removed) {
                    logD(
                        "Removed active task=%d displayId=%d deskId=%d",
                        taskId,
                    displayId,
                        desk.displayId,
                        desk.deskId,
                    )
                affectedDisplays.add(displayId)
                    affectedDisplays.add(desk.displayId)
                }
            }
        affectedDisplays.forEach { displayId -> updateActiveTasksListeners(displayId) }
    }

    private fun removeActiveTaskFromDesk(
        deskId: Int,
        taskId: Int,
        notifyListeners: Boolean = true,
    ): Boolean {
        val desk = desktopData.getDesk(deskId) ?: return false
        if (desk.activeTasks.remove(taskId)) {
            logD("Removed active task=%d from deskId=%d", taskId, desk.deskId)
            if (notifyListeners) {
                updateActiveTasksListeners(desk.displayId)
            }
            return true
        }
        return false
    }

    /**
     * Adds given task to the closing task list for [displayId]'s active desk.
     *
@@ -323,10 +352,22 @@ class DesktopRepository(

    fun isActiveTask(taskId: Int) = desksSequence().any { taskId in it.activeTasks }

    @VisibleForTesting
    fun isActiveTaskInDesk(taskId: Int, deskId: Int): Boolean {
        val desk = desktopData.getDesk(deskId) ?: return false
        return taskId in desk.activeTasks
    }

    fun isClosingTask(taskId: Int) = desksSequence().any { taskId in it.closingTasks }

    fun isVisibleTask(taskId: Int) = desksSequence().any { taskId in it.visibleTasks }

    @VisibleForTesting
    fun isVisibleTaskInDesk(taskId: Int, deskId: Int): Boolean {
        val desk = desktopData.getDesk(deskId) ?: return false
        return taskId in desk.visibleTasks
    }

    fun isMinimizedTask(taskId: Int) = desksSequence().any { taskId in it.minimizedTasks }

    /**
@@ -416,12 +457,19 @@ class DesktopRepository(
    /** Removes task from visible tasks of all desks except [excludedDeskId]. */
    private fun removeVisibleTask(taskId: Int, excludedDeskId: Int? = null) {
        desktopData.forAllDesks { displayId, desk ->
            if (desk.deskId != excludedDeskId && desk.visibleTasks.remove(taskId)) {
                notifyVisibleTaskListeners(displayId, desk.visibleTasks.size)
            if (desk.deskId != excludedDeskId) {
                removeVisibleTaskFromDesk(deskId = desk.deskId, taskId = taskId)
            }
        }
    }

    private fun removeVisibleTaskFromDesk(deskId: Int, taskId: Int) {
        val desk = desktopData.getDesk(deskId) ?: return
        if (desk.visibleTasks.remove(taskId)) {
            notifyVisibleTaskListeners(desk.displayId, desk.visibleTasks.size)
        }
    }

    /**
     * Updates visibility of a freeform task with [taskId] on [displayId] and notifies listeners.
     *
@@ -577,15 +625,26 @@ class DesktopRepository(
    /**
     * Set whether the given task is the full-immersive task in this display's active desk.
     *
     * TODO: b/389960283 - add explicit [deskId] argument.
     * TODO: b/389960283 - consider forcing callers to use [setTaskInFullImmersiveStateInDesk] with
     *   an explicit desk id instead of using this function and defaulting to the active one.
     */
    fun setTaskInFullImmersiveState(displayId: Int, taskId: Int, immersive: Boolean) {
        val desktopData = desktopData.getActiveDesk(displayId) ?: return
        val activeDesk = desktopData.getActiveDesk(displayId) ?: return
        setTaskInFullImmersiveStateInDesk(
            deskId = activeDesk.deskId,
            taskId = taskId,
            immersive = immersive,
        )
    }

    /** Sets whether the given task is the full-immersive task in the given desk. */
    fun setTaskInFullImmersiveStateInDesk(deskId: Int, taskId: Int, immersive: Boolean) {
        val desk = desktopData.getDesk(deskId) ?: return
        if (immersive) {
            desktopData.fullImmersiveTaskId = taskId
            desk.fullImmersiveTaskId = taskId
        } else {
            if (desktopData.fullImmersiveTaskId == taskId) {
                desktopData.fullImmersiveTaskId = null
            if (desk.fullImmersiveTaskId == taskId) {
                desk.fullImmersiveTaskId = null
            }
        }
    }
@@ -675,7 +734,8 @@ class DesktopRepository(
    /**
     * Minimizes the task for [taskId] and [displayId]'s active display.
     *
     * TODO: b/389960283 - add explicit [deskId] argument.
     * TODO: b/389960283 - consider forcing callers to use [minimizeTaskInDesk] with an explicit
     *   desk id instead of using this function and defaulting to the active one.
     */
    fun minimizeTask(displayId: Int, taskId: Int) {
        if (displayId == INVALID_DISPLAY) {
@@ -684,32 +744,41 @@ class DesktopRepository(
            getDisplayIdForTask(taskId)?.let { minimizeTask(it, taskId) }
                ?: logW("Minimize task: No display id found for task: taskId=%d", taskId)
            return
        } else {
            logD("Minimize Task: display=%d, task=%d", displayId, taskId)
            desktopData.getActiveDesk(displayId)?.minimizedTasks?.add(taskId)
                ?: logD("Minimize task: No active desk found for task: taskId=%d", taskId)
        }
        updateTask(displayId, taskId, isVisible = false)
        val deskId = desktopData.getActiveDesk(displayId)?.deskId
        if (deskId == null) {
            logD("Minimize task: No active desk found for task: taskId=%d", taskId)
            return
        }
        minimizeTaskInDesk(displayId, deskId, taskId)
    }

    /** Minimizes the task in its desk. */
    @VisibleForTesting
    fun minimizeTaskInDesk(displayId: Int, deskId: Int, taskId: Int) {
        logD("Minimize Task: displayId=%d deskId=%d, task=%d", displayId, deskId, taskId)
        desktopData.getDesk(deskId)?.minimizedTasks?.add(taskId)
            ?: logD("Minimize task: No active desk found for task: taskId=%d", taskId)
        updateTaskInDesk(displayId, deskId, taskId, isVisible = false)
        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
            updatePersistentRepository(displayId)
            updatePersistentRepositoryForDesk(deskId)
        }
    }

    /**
     * Unminimizes the task for [taskId] and [displayId].
     *
     * TODO: b/389960283 - consider adding an explicit [deskId] argument.
     * TODO: b/389960283 - consider using [unminimizeTaskFromDesk] instead.
     */
    fun unminimizeTask(displayId: Int, taskId: Int) {
        logD("Unminimize Task: display=%d, task=%d", displayId, taskId)
        var removed = false
        desktopData.forAllDesks(displayId) { desk ->
            if (desk.minimizedTasks.remove(taskId)) {
                removed = true
            }
        desktopData.forAllDesks(displayId) { desk -> unminimizeTaskFromDesk(desk.deskId, taskId) }
    }
        if (!removed) {
            logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)

    private fun unminimizeTaskFromDesk(deskId: Int, taskId: Int) {
        logD("Unminimize Task: deskId=%d, taskId=%d", deskId, taskId)
        if (desktopData.getDesk(deskId)?.minimizedTasks?.remove(taskId) != true) {
            logW("Unminimize Task: deskId=%d, taskId=%d, no task data", deskId, taskId)
        }
    }

@@ -730,7 +799,7 @@ class DesktopRepository(
     * Removes [taskId] from the respective display. If [INVALID_DISPLAY], the original display id
     * will be looked up from the task id.
     *
     * TODO: b/389960283 - consider adding an explicit [deskId] argument.
     * TODO: b/389960283 - consider using [removeTaskFromDesk] instead.
     */
    fun removeTask(displayId: Int, taskId: Int) {
        logD("Removes freeform task: taskId=%d", taskId)
@@ -746,6 +815,19 @@ class DesktopRepository(
    private fun removeTaskFromDisplay(displayId: Int, taskId: Int) {
        logD("Removes freeform task: taskId=%d, displayId=%d", taskId, displayId)
        desktopData.forAllDesks(displayId) { desk ->
            removeTaskFromDesk(deskId = desk.deskId, taskId = taskId)
        }
    }

    /** Removes the given task from the given desk. */
    fun removeTaskFromDesk(deskId: Int, taskId: Int) {
        logD("removeTaskFromDesk: deskId=%d, taskId=%d", taskId, deskId)
        // TODO: b/362720497 - consider not clearing bounds on any removal, such as when moving
        //  it between desks. It might be better to allow restoring to the previous bounds as long
        //  as they're valid (probably valid if in the same display).
        boundsBeforeMaximizeByTaskId.remove(taskId)
        boundsBeforeFullImmersiveByTaskId.remove(taskId)
        val desk = desktopData.getDesk(deskId) ?: return
        if (desk.freeformTasksInZOrder.remove(taskId)) {
            logD(
                "Remaining freeform tasks in desk: %d, tasks: %s",
@@ -753,17 +835,13 @@ class DesktopRepository(
                desk.freeformTasksInZOrder.toDumpString(),
            )
        }
        }
        boundsBeforeMaximizeByTaskId.remove(taskId)
        boundsBeforeFullImmersiveByTaskId.remove(taskId)
        // Remove task from unminimized task if it is minimized.
        unminimizeTask(displayId, taskId)
        unminimizeTaskFromDesk(deskId, taskId)
        // Mark task as not in immersive if it was immersive.
        setTaskInFullImmersiveState(displayId = displayId, taskId = taskId, immersive = false)
        removeActiveTask(taskId)
        removeVisibleTask(taskId)
        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
            updatePersistentRepository(displayId)
        setTaskInFullImmersiveStateInDesk(deskId = deskId, taskId = taskId, immersive = false)
        removeActiveTaskFromDesk(deskId = deskId, taskId = taskId)
        removeVisibleTaskFromDesk(deskId = deskId, taskId = taskId)
        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue) {
            updatePersistentRepositoryForDesk(desk.deskId)
        }
    }

@@ -833,11 +911,18 @@ class DesktopRepository(
    private fun updatePersistentRepository(displayId: Int) {
        val desks = desktopData.desksSequence(displayId).map { desk -> desk.deepCopy() }.toList()
        mainCoroutineScope.launch {
            desks.forEach { desk ->
            desks.forEach { desk -> updatePersistentRepositoryForDesk(desk) }
        }
    }

    private fun updatePersistentRepositoryForDesk(deskId: Int) {
        val desk = desktopData.getDesk(deskId)?.deepCopy() ?: return
        mainCoroutineScope.launch { updatePersistentRepositoryForDesk(desk) }
    }

    private suspend fun updatePersistentRepositoryForDesk(desk: Desk) {
        try {
            persistentRepository.addOrUpdateDesktop(
                        // Use display id as desk id for now since only once desk per display
                        // is supported.
                userId = userId,
                desktopId = desk.deskId,
                visibleTasks = desk.visibleTasks,
@@ -851,8 +936,6 @@ class DesktopRepository(
            )
        }
    }
        }
    }

    internal fun dump(pw: PrintWriter, prefix: String) {
        val innerPrefix = "$prefix  "
@@ -867,21 +950,27 @@ class DesktopRepository(
        desktopData
            .desksSequence()
            .groupBy { it.displayId }
            .forEach { (displayId, desks) ->
            .map { (displayId, desks) ->
                Triple(displayId, desktopData.getActiveDesk(displayId)?.deskId, desks)
            }
            .forEach { (displayId, activeDeskId, desks) ->
                pw.println("${prefix}Display #$displayId:")
                pw.println("${innerPrefix}activeDesk=$activeDeskId")
                pw.println("${innerPrefix}desks:")
                val desksPrefix = "$innerPrefix  "
                desks.forEach { desk ->
                    pw.println("${innerPrefix}Desk #${desk.deskId}:")
                    pw.print("$innerPrefix  activeTasks=")
                    pw.println("${desksPrefix}Desk #${desk.deskId}:")
                    pw.print("$desksPrefix  activeTasks=")
                    pw.println(desk.activeTasks.toDumpString())
                    pw.print("$innerPrefix  visibleTasks=")
                    pw.print("$desksPrefix  visibleTasks=")
                    pw.println(desk.visibleTasks.toDumpString())
                    pw.print("$innerPrefix  freeformTasksInZOrder=")
                    pw.print("$desksPrefix  freeformTasksInZOrder=")
                    pw.println(desk.freeformTasksInZOrder.toDumpString())
                    pw.print("$innerPrefix  minimizedTasks=")
                    pw.print("$desksPrefix  minimizedTasks=")
                    pw.println(desk.minimizedTasks.toDumpString())
                    pw.print("$innerPrefix  fullImmersiveTaskId=")
                    pw.print("$desksPrefix  fullImmersiveTaskId=")
                    pw.println(desk.fullImmersiveTaskId)
                    pw.print("$innerPrefix  topTransparentFullscreenTaskId=")
                    pw.print("$desksPrefix  topTransparentFullscreenTaskId=")
                    pw.println(desk.topTransparentFullscreenTaskId)
                }
            }
@@ -911,6 +1000,9 @@ class DesktopRepository(
        /** Sets the given desk as the active desk in the given display. */
        fun setActiveDesk(displayId: Int, deskId: Int)

        /** Sets the desk as inactive if it was active. */
        fun setDeskInactive(deskId: Int)

        /**
         * Returns the default desk in the given display. Useful when the system wants to activate a
         * desk but doesn't care about which one it activates (e.g. when putting a window into a
@@ -991,6 +1083,11 @@ class DesktopRepository(
            // existence of visible desktop windows, among other factors.
        }

        override fun setDeskInactive(deskId: Int) {
            // No-op, in single-desk setups, which desktop is "active" is determined by the
            // existence of visible desktop windows, among other factors.
        }

        override fun getDefaultDesk(displayId: Int): Desk = getDesk(deskId = displayId)

        override fun getAllActiveDesks(): Set<Desk> =
@@ -1059,6 +1156,14 @@ class DesktopRepository(
            display.activeDeskId = desk.deskId
        }

        override fun setDeskInactive(deskId: Int) {
            desktopDisplays.forEach { id, display ->
                if (display.activeDeskId == deskId) {
                    display.activeDeskId = null
                }
            }
        }

        override fun getDefaultDesk(displayId: Int): Desk? {
            val display = desktopDisplays[displayId] ?: return null
            return display.orderedDesks.find { it.deskId == display.activeDeskId }
+6 −0
Original line number Diff line number Diff line
@@ -42,6 +42,12 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser
            desktopUserRepositories.getProfile(taskInfo.userId)
        if (!desktopRepository.isActiveTask(taskInfo.taskId)) return

        // TODO: b/394281403 - with multiple desks, it's possible to have a non-freeform task
        //  inside a desk, so this should be decoupled from windowing mode.
        //  Also, changes in/out of desks are handled by the [DesksTransitionObserver], which has
        //  more specific information about the desk involved in the transition, which might be
        //  more accurate than assuming it's always the default/active desk in the display, as this
        //  method does.
        // Case 1: Freeform task is changed in Desktop Mode.
        if (isFreeformTask(taskInfo)) {
            if (taskInfo.isVisible) {
+118 −36

File changed.

Preview size limit exceeded, changes collapsed.

+2 −1
Original line number Diff line number Diff line
@@ -106,7 +106,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
     * @param position               Position of the task when transition is started
     * @param onAnimationEndCallback to be called after animation
     */
    public void startTransition(@NonNull DesktopModeTransitionSource transitionSource,
    public IBinder startTransition(@NonNull DesktopModeTransitionSource transitionSource,
            @NonNull WindowContainerTransaction wct, Point position,
            Function0<Unit> onAnimationEndCallback) {
        mPosition = position;
@@ -114,6 +114,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
        final IBinder token = mTransitions.startTransition(getExitTransitionType(transitionSource),
                wct, this);
        mPendingTransitionTokens.add(token);
        return token;
    }

    @Override
Loading