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

Commit 0526e664 authored by Matt Sziklay's avatar Matt Sziklay Committed by Android (Google) Code Review
Browse files

Merge "[4/N] Restore desks on display reconnect." into main

parents 7c9e9daa ff2f37ff
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -64,6 +64,14 @@ class DesktopDisplayEventHandler(

    private val onDisplayAreaChangeListener = OnDisplayAreaChangeListener { displayId ->
        logV("displayAreaChanged in displayId=%d", displayId)
        val uniqueDisplayId = displayController.getDisplay(displayId)?.uniqueId
        uniqueDisplayId?.let {
            uniqueIdByDisplayId[displayId] = it
            if (desktopUserRepositories.current.hasPreservedDisplayForUniqueDisplayId(it)) {
                desktopTasksController.restoreDisplay(displayId, it)
                return@OnDisplayAreaChangeListener
            }
        }
        createDefaultDesksIfNeeded(displayIds = listOf(displayId), userId = null)
    }

+33 −1
Original line number Diff line number Diff line
@@ -204,8 +204,40 @@ class DesktopRepository(
        preservedDisplaysByUniqueId[uniqueId] = preservedDisplay
    }

    /** Removes the specified preserved display. */
    fun removePreservedDisplay(uniqueDisplayId: String) {
        preservedDisplaysByUniqueId.remove(uniqueDisplayId)
    }

    /** Whether or not the given uniqueDisplayId matches a display that is being preserved. */
    fun hasPreservedDisplayForUniqueDisplayId(uniqueDisplayId: String): Boolean =
        preservedDisplaysByUniqueId.containsKey(uniqueDisplayId)

    /** Returns all active tasks on the preserved display separated by desk. */
    fun getPreservedTasksByDeskIdInZOrder(uniqueDisplayId: String): Map<Int, List<Int>> {
        val preservedDesks =
            preservedDisplaysByUniqueId[uniqueDisplayId]?.orderedDesks ?: emptySet()
        val tasksByDeskId = mutableMapOf<Int, List<Int>>()
        for (desk in preservedDesks) {
            tasksByDeskId[desk.deskId] = desk.freeformTasksInZOrder
        }
        return tasksByDeskId
    }

    /** Returns the active desk on the preserved display for the specified unique display id. */
    fun getPreservedActiveDesk(uniqueDisplayId: String): Int? =
        preservedDisplaysByUniqueId[uniqueDisplayId]?.activeDeskId

    /**
     * Checks if the provided task is minimized on the preserved display with the provided
     * uniqueDisplayId.
     */
    fun isPreservedTaskMinimized(uniqueDisplayId: String, taskId: Int): Boolean =
        preservedDisplaysByUniqueId[uniqueDisplayId]?.orderedDesks?.any { desk ->
            desk.minimizedTasks.contains(taskId)
        } ?: false

    /** Returns the bounds of all tasks in all desks of the preserved display. */
    @VisibleForTesting
    fun getPreservedTaskBounds(uniqueDisplayId: String): Map<Int, Rect> {
        val combinedBoundsMap = mutableMapOf<Int, Rect>()
        val orderedDesks =
+105 −0
Original line number Diff line number Diff line
@@ -690,6 +690,10 @@ class DesktopTasksController(
        destinationDisplayId: Int,
        transition: IBinder,
    ): WindowContainerTransaction {
        logD(
            "onDisplayDisconnect: disconnectedDisplayId=$disconnectedDisplayId, " +
                "destinationDisplayId=$destinationDisplayId"
        )
        preserveDisplayRequestHandler?.requestPreserveDisplay(disconnectedDisplayId)
        // TODO: b/406320371 - Verify this works with non-system users once the underlying bug is
        //  resolved.
@@ -698,6 +702,13 @@ class DesktopTasksController(
        //  Additionally, investigate why wallpaper goes to front for inactive users.
        val desktopModeSupportedOnDisplay =
            desktopState.isDesktopModeSupportedOnDisplay(destinationDisplayId)
        val destDisplayLayout = displayController.getDisplayLayout(destinationDisplayId)
        if (destDisplayLayout == null) {
            logE(
                "onDisplayDisconnect: no display layout found for " +
                    "destinationDisplayId=$destinationDisplayId"
            )
        }
        snapEventHandler.onDisplayDisconnected(disconnectedDisplayId, desktopModeSupportedOnDisplay)
        removeWallpaperTask(wct, disconnectedDisplayId)
        removeHomeTask(wct, disconnectedDisplayId)
@@ -709,6 +720,7 @@ class DesktopTasksController(
                    val deskTasks = desktopRepository.getActiveTaskIdsInDesk(deskId)
                    // Remove desk if it's empty.
                    if (deskTasks.isEmpty()) {
                        logD("onDisplayDisconnect: removing empty desk=$deskId")
                        desksOrganizer.removeDesk(wct, deskId, desktopRepository.userId)
                        desksTransitionObserver.addPendingTransition(
                            DeskTransition.RemoveDesk(
@@ -721,10 +733,21 @@ class DesktopTasksController(
                            )
                        )
                    } else {
                        logD(
                            "onDisplayDisconnect: reparenting desk=$deskId to " +
                                "display=$destinationDisplayId"
                        )
                        // Otherwise, reparent it to the destination display.
                        val toTop =
                            deskTasks.contains(focusTransitionObserver.globallyFocusedTaskId)
                        desksOrganizer.moveDeskToDisplay(wct, deskId, destinationDisplayId, toTop)
                        val taskIds = desktopRepository.getActiveTaskIdsInDesk(deskId)
                        for (taskId in taskIds) {
                            val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: continue
                            destDisplayLayout?.densityDpi()?.let {
                                wct.setDensityDpi(task.token, it)
                            }
                        }
                        desksTransitionObserver.addPendingTransition(
                            DeskTransition.ChangeDeskDisplay(
                                transition,
@@ -742,6 +765,7 @@ class DesktopTasksController(
                    }
                }
            } else {
                logD("onDisplayDisconnect: moving tasks to non-desktop display")
                // Desktop not supported on display; reparent tasks to display area, remove desk.
                val tdaInfo =
                    checkNotNull(
@@ -758,6 +782,7 @@ class DesktopTasksController(
                            tdaInfo.token,
                            focusTransitionObserver.globallyFocusedTaskId == task.taskId,
                        )
                        destDisplayLayout?.densityDpi()?.let { wct.setDensityDpi(task.token, it) }
                    }
                    desksOrganizer.removeDesk(wct, deskId, userId)
                    desksTransitionObserver.addPendingTransition(
@@ -779,6 +804,86 @@ class DesktopTasksController(
        return wct
    }

    /**
     * Restore a display based on info that was stored on disconnect.
     *
     * TODO: b/365873835 - Restore for all users, not just current.
     */
    fun restoreDisplay(displayId: Int, uniqueDisplayId: String) {
        if (!DesktopExperienceFlags.ENABLE_DISPLAY_RECONNECT_INTERACTION.isTrue) return
        logD("restoreDisplay: displayId=$displayId, uniqueDisplayId=$uniqueDisplayId")
        // TODO: b/365873835 - Utilize DesktopTask data class once it is
        //  implemented in DesktopRepository.
        val preservedTaskIdsByDeskId =
            taskRepository.getPreservedTasksByDeskIdInZOrder(uniqueDisplayId)
        val boundsByTaskId = taskRepository.getPreservedTaskBounds(uniqueDisplayId)
        val activeDeskId = taskRepository.getPreservedActiveDesk(uniqueDisplayId)
        val wct = WindowContainerTransaction()
        var runOnTransitStart: RunOnTransitStart? = null
        val destDisplayLayout = displayController.getDisplayLayout(displayId) ?: return

        mainScope.launch {
            preservedTaskIdsByDeskId.forEach { (preservedDeskId, preservedTaskIds) ->
                val newDeskId =
                    createDeskSuspending(
                        displayId = displayId,
                        userId = userId,
                        enforceDeskLimit = true,
                    )
                logD(
                    "restoreDisplay: created new desk deskId=$newDeskId " +
                        "from preserved deskId=$preservedDeskId"
                )

                if (preservedDeskId == activeDeskId) {
                    runOnTransitStart = addDeskActivationChanges(deskId = newDeskId, wct = wct)
                }

                preservedTaskIds.asReversed().forEach { taskId ->
                    addRestoreTaskToDeskChanges(
                        wct,
                        destDisplayLayout,
                        newDeskId,
                        taskId,
                        uniqueDisplayId,
                        boundsByTaskId[taskId],
                    )
                }
            }
            val transition = transitions.startTransition(TRANSIT_CHANGE, wct, null)
            runOnTransitStart?.invoke(transition)
            taskRepository.removePreservedDisplay(uniqueDisplayId)
        }
    }

    private fun addRestoreTaskToDeskChanges(
        wct: WindowContainerTransaction,
        destinationDisplayLayout: DisplayLayout,
        deskId: Int,
        taskId: Int,
        uniqueDisplayId: String,
        taskBounds: Rect?,
    ) {
        logD(
            "addRestoreTaskToDeskChanges: taskId=$taskId; deskId=$deskId; " +
                "taskBounds=$taskBounds; uniqueDisplayId=$uniqueDisplayId"
        )
        val minimized = taskRepository.isPreservedTaskMinimized(uniqueDisplayId, taskId)
        val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
        if (task == null) {
            logE("restoreDisplay: Could not find running task info for taskId=$taskId.")
            return
        }
        desksOrganizer.moveTaskToDesk(wct, deskId, task, minimized = minimized)
        wct.setDensityDpi(task.token, destinationDisplayLayout.densityDpi())
        taskBounds?.let { wct.setBounds(task.token, it) }

        if (!minimized) {
            // Bring display to front if task is not minimized to ensure display focus.
            wct.reorder(task.token, /* onTop= */ true, /* includingParents= */ true)
        }
    }

    /**
     * Handle desk operations when disconnecting a display and all desks on that display are moving
     * to a display that supports desks. The previously focused display will determine which desk
+52 −9
Original line number Diff line number Diff line
@@ -10156,6 +10156,53 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        return wct
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION,
        Flags.FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
    )
    fun restoreDisplay_restoresTasksWithCorrectBounds() =
        testScope.runTest {
            taskRepository.addDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID)
            taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = DISCONNECTED_DESK_ID)
            val firstTaskBounds = Rect(100, 300, 1000, 1200)
            val firstTask =
                setUpFreeformTask(
                    displayId = SECOND_DISPLAY,
                    deskId = DISCONNECTED_DESK_ID,
                    bounds = firstTaskBounds,
                )
            val secondTaskBounds = Rect(400, 400, 1600, 900)
            val secondTask =
                setUpFreeformTask(
                    displayId = SECOND_DISPLAY,
                    deskId = DISCONNECTED_DESK_ID,
                    bounds = secondTaskBounds,
                )
            val wctCaptor = argumentCaptor<WindowContainerTransaction>()
            taskRepository.preserveDisplay(SECOND_DISPLAY, SECOND_DISPLAY_UNIQUE_ID)
            taskRepository.onDeskDisplayChanged(DISCONNECTED_DESK_ID, DEFAULT_DISPLAY)
            whenever(desksOrganizer.createDesk(eq(SECOND_DISPLAY_ON_RECONNECT), any(), any()))
                .thenAnswer { invocation ->
                    (invocation.arguments[2] as DesksOrganizer.OnCreateCallback).onCreated(
                        deskId = 5
                    )
                }

            controller.restoreDisplay(SECOND_DISPLAY_ON_RECONNECT, SECOND_DISPLAY_UNIQUE_ID)
            runCurrent()

            verify(transitions).startTransition(anyInt(), wctCaptor.capture(), anyOrNull())
            val wct = wctCaptor.firstValue
            assertThat(findBoundsChange(wct, firstTask)).isEqualTo(firstTaskBounds)
            assertThat(findBoundsChange(wct, secondTask)).isEqualTo(secondTaskBounds)
            wct.assertReorder(task = firstTask, toTop = true, includingParents = true)
            wct.assertReorder(task = secondTask, toTop = true, includingParents = true)
            verify(desksOrganizer).moveTaskToDesk(any(), anyInt(), eq(firstTask), eq(false))
            verify(desksOrganizer).moveTaskToDesk(any(), anyInt(), eq(secondTask), eq(false))
        }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
@@ -10563,7 +10610,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()

    private fun setUpFreeformTask(
        displayId: Int = DEFAULT_DISPLAY,
        bounds: Rect? = null,
        bounds: Rect = TASK_BOUNDS,
        active: Boolean = true,
        background: Boolean = false,
        deskId: Int? = null,
@@ -10580,15 +10627,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
            whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
        }
        if (deskId != null) {
            taskRepository.addTaskToDesk(
                displayId,
                deskId,
                task.taskId,
                isVisible = active,
                TASK_BOUNDS,
            )
            taskRepository.addTaskToDesk(displayId, deskId, task.taskId, isVisible = active, bounds)
        } else {
            taskRepository.addTask(displayId, task.taskId, isVisible = active, TASK_BOUNDS)
            taskRepository.addTask(displayId, task.taskId, isVisible = active, bounds)
        }
        if (!background) {
            runningTasks.add(task)
@@ -10817,6 +10858,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()

    private companion object {
        const val SECOND_DISPLAY = 2
        const val SECOND_DISPLAY_ON_RECONNECT = 3
        const val SECOND_DISPLAY_UNIQUE_ID = "UNIQUE_ID"
        val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
        const val MAX_TASK_LIMIT = 6
        private const val TASKBAR_FRAME_HEIGHT = 200