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

Commit ff2f37ff authored by Matt Sziklay's avatar Matt Sziklay Committed by mattsziklay
Browse files

[4/N] Restore desks on display reconnect.

When a display preserved in DesktopRepository is reconnected, restore the desk to the display, adding tasks from the previously active desk with the bounds they had on disconnect.

Additionally, adds more logging and densityDpi changes to both
disconnect and reconnect.

Bug: 365873835
Test: Disconnect a display with desks, then reconnect it
Flag: com.android.window.flags.enable_display_disconnect_interaction
Flag: com.android.window.flags.enable_display_reconnect_interaction

Change-Id: Id0bde38ede08c574b41dd5314da4332d8dea23c1
parent 743bc3d4
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
@@ -10165,6 +10165,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,
@@ -10572,7 +10619,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,
@@ -10589,15 +10636,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)
@@ -10826,6 +10867,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