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

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

Projected mode: Do not alter internal display tasks on disconnect/reconnect.

Currently on disconnect, the globally focused task is moved to the top of the internal display for both projected and extended display modes. This CL instead moves all external display tasks to the bottom, leaving existing internal display tasks the same.

Similarly, on reconnect, we skip task restoration for taskIds that are currently focused on the internal display. If that means we restore no tasks, then we bypass restoreDisplay altogether.

Bug: 435443831
Test: Manual
Flag: com.android.window.flags.enable_display_disconnect_interaction
Flag: com.android.window.flags.enable_display_reconnect_interaction
Change-Id: Iaf463b9c62deadda9814e88ac84fe7a05412ecc0
parent d1819fee
Loading
Loading
Loading
Loading
+25 −11
Original line number Diff line number Diff line
@@ -188,12 +188,28 @@ class DesktopDisplayEventHandler(
    private fun handlePotentialReconnect(displayId: Int): Boolean {
        val uniqueDisplayId = displayController.getDisplay(displayId)?.uniqueId ?: return false
        uniqueIdByDisplayId[displayId] = uniqueDisplayId
        val currentUserRepository = desktopUserRepositories.current
        if (
            DesktopExperienceFlags.ENABLE_DISPLAY_RECONNECT_INTERACTION.isTrue &&
                desktopUserRepositories.current.hasPreservedDisplayForUniqueDisplayId(
                    uniqueDisplayId
                )
            !DesktopExperienceFlags.ENABLE_DISPLAY_RECONNECT_INTERACTION.isTrue ||
                !currentUserRepository.hasPreservedDisplayForUniqueDisplayId(uniqueDisplayId)
        ) {
            return false
        }
        val preservedTasks = currentUserRepository.getPreservedTasks(uniqueDisplayId)
        // Projected mode: Do not move anything focused on the internal display.
        if (!desktopState.isDesktopModeSupportedOnDisplay(DEFAULT_DISPLAY)) {
            val focusedDefaultDisplayTaskIds =
                desktopTasksController
                    .getFocusedNonDesktopTasks(DEFAULT_DISPLAY, currentUserRepository.userId)
                    .map { task -> task.taskId }
            preservedTasks.filterNot { taskId -> focusedDefaultDisplayTaskIds.contains(taskId) }
        }
        if (preservedTasks.isEmpty()) {
            // The preserved display is normally removed at the end of restoreDisplay.
            // If we don't restore anything, remove it here instead.
            currentUserRepository.removePreservedDisplay(uniqueDisplayId)
            return false
        }
        desktopTasksController.restoreDisplay(
            displayId = displayId,
            uniqueDisplayId = uniqueDisplayId,
@@ -201,8 +217,6 @@ class DesktopDisplayEventHandler(
        )
        return true
    }
        return false
    }

    override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
+151 −116
Original line number Diff line number Diff line
@@ -44,7 +44,6 @@ import android.os.Binder
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.os.SystemProperties
import android.os.Trace
import android.os.UserHandle
import android.os.UserManager
@@ -127,6 +126,7 @@ import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.data.DesktopRepository
import com.android.wm.shell.desktopmode.data.DesktopRepository.Companion.INVALID_DESK_ID
import com.android.wm.shell.desktopmode.data.DesktopRepository.DeskChangeListener
import com.android.wm.shell.desktopmode.data.DesktopRepository.VisibleTasksListener
@@ -750,7 +750,7 @@ class DesktopTasksController(
            "onDisplayDisconnect: disconnectedDisplayId=$disconnectedDisplayId, " +
                "destinationDisplayId=$destinationDisplayId"
        )
        val runOnTransitStartSet = mutableListOf<RunOnTransitStart>()
        val runOnTransitStartList = mutableListOf<RunOnTransitStart>()
        preserveDisplayRequestHandler?.requestPreserveDisplay(disconnectedDisplayId)
        // TODO: b/406320371 - Verify this works with non-system users once the underlying bug is
        //  resolved.
@@ -772,18 +772,54 @@ class DesktopTasksController(
            val userId = desktopRepository.userId
            val deskIds = desktopRepository.getDeskIds(disconnectedDisplayId).toList()
            if (desktopModeSupportedOnDisplay) {
                handleExtendedModeDisconnect(
                    desktopRepository,
                    wct,
                    runOnTransitStartList,
                    deskIds,
                    disconnectedDisplayId,
                    destinationDisplayId,
                    destDisplayLayout,
                    userId,
                )
            } else {
                handleProjectedModeDisconnect(
                    desktopRepository,
                    wct,
                    runOnTransitStartList,
                    deskIds,
                    disconnectedDisplayId,
                    destinationDisplayId,
                    destDisplayLayout,
                    userId,
                )
            }
        }
        return { transition ->
            for (runOnTransitStart in runOnTransitStartList) {
                runOnTransitStart(transition)
            }
        }
    }

    private fun handleExtendedModeDisconnect(
        desktopRepository: DesktopRepository,
        wct: WindowContainerTransaction,
        runOnTransitStartList: MutableList<RunOnTransitStart>,
        deskIds: List<Int>,
        disconnectedDisplayId: Int,
        destinationDisplayId: Int,
        destDisplayLayout: DisplayLayout?,
        userId: Int,
    ) {
        // Desktop supported on display; reparent desks, focused desk on top.
        for (deskId in deskIds) {
            val deskTasks = desktopRepository.getActiveTaskIdsInDesk(deskId)
            // Remove desk if it's empty.
            if (deskTasks.isEmpty()) {
                        logD(
                            "onDisplayDisconnect: removing empty desk=%d of user=%d",
                            deskId,
                            userId,
                        )
                logD("onDisplayDisconnect: removing empty desk=%d of user=%d", deskId, userId)
                desksOrganizer.removeDesk(wct, deskId, userId)
                        runOnTransitStartSet.add { transition ->
                runOnTransitStartList.add { transition ->
                    desksTransitionObserver.addPendingTransition(
                        DeskTransition.RemoveDesk(
                            token = transition,
@@ -805,18 +841,15 @@ class DesktopTasksController(
                    userId,
                )
                // Otherwise, reparent it to the destination display.
                        val toTop =
                            deskTasks.contains(focusTransitionObserver.globallyFocusedTaskId)
                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)
                            }
                    destDisplayLayout?.densityDpi()?.let { wct.setDensityDpi(task.token, it) }
                    applyFreeformDisplayChange(wct, task, destinationDisplayId, deskId)
                }
                        runOnTransitStartSet.add { transition ->
                runOnTransitStartList.add { transition ->
                    desksTransitionObserver.addPendingTransition(
                        DeskTransition.ChangeDeskDisplay(
                            token = transition,
@@ -833,31 +866,36 @@ class DesktopTasksController(
                        wct = wct,
                        toTop = toTop,
                    )
                            ?.let { runOnTransitStartSet.add(it) }
                    ?.let { runOnTransitStartList.add(it) }
            }
        }
            } else {
                logD("onDisplayDisconnect: moving tasks to non-desktop display")
    }

    private fun handleProjectedModeDisconnect(
        desktopRepository: DesktopRepository,
        wct: WindowContainerTransaction,
        runOnTransitStartList: MutableList<RunOnTransitStart>,
        deskIds: List<Int>,
        disconnectedDisplayId: Int,
        destinationDisplayId: Int,
        destDisplayLayout: DisplayLayout?,
        userId: Int,
    ) {
        logD("handleProjectedModeDisconnect: moving tasks to non-desktop display")
        // Desktop not supported on display; reparent tasks to display area, remove desk.
        val tdaInfo =
                    checkNotNull(
                        rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(destinationDisplayId)
                    ) {
            checkNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(destinationDisplayId)) {
                "Expected to find displayAreaInfo for displayId=$destinationDisplayId"
            }
        for (deskId in deskIds) {
            val taskIds = desktopRepository.getActiveTaskIdsInDesk(deskId)
            for (taskId in taskIds) {
                val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: continue
                        wct.reparent(
                            task.token,
                            tdaInfo.token,
                            focusTransitionObserver.globallyFocusedTaskId == task.taskId,
                        )
                wct.reparent(task.token, tdaInfo.token, /* onTop= */ false)
                destDisplayLayout?.densityDpi()?.let { wct.setDensityDpi(task.token, it) }
            }
            desksOrganizer.removeDesk(wct, deskId, userId)
                    runOnTransitStartSet.add { transition ->
            runOnTransitStartList.add { transition ->
                desksTransitionObserver.addPendingTransition(
                    DeskTransition.RemoveDesk(
                        token = transition,
@@ -880,13 +918,6 @@ class DesktopTasksController(
            }
        }
    }
        }
        return { transition ->
            for (runOnTransitStart in runOnTransitStartSet) {
                runOnTransitStart(transition)
            }
        }
    }

    /**
     * Restore a display based on info that was stored on disconnect.
@@ -911,6 +942,8 @@ class DesktopTasksController(
        val destDisplayLayout = displayController.getDisplayLayout(displayId) ?: return
        val tilingReconnectHandler =
            TilingDisplayReconnectEventHandler(repository, snapEventHandler, transitions, displayId)
        val excludedTasks =
            getFocusedNonDesktopTasks(DEFAULT_DISPLAY, userId).map { task -> task.taskId }
        mainScope.launch {
            preservedTaskIdsByDeskId.forEach { (preservedDeskId, preservedTaskIds) ->
                val newDeskId =
@@ -935,6 +968,7 @@ class DesktopTasksController(
                }

                preservedTaskIds.asReversed().forEach { taskId ->
                    if (!excludedTasks.contains(taskId)) {
                        addRestoreTaskToDeskChanges(
                            wct = wct,
                            destinationDisplayLayout = destDisplayLayout,
@@ -945,6 +979,7 @@ class DesktopTasksController(
                            taskBounds = boundsByTaskId[taskId],
                        )
                    }
                }

                val preservedTilingData =
                    repository.getPreservedTilingData(uniqueDisplayId, preservedDeskId)
+13 −0
Original line number Diff line number Diff line
@@ -154,6 +154,19 @@ class DesktopRepository(
        return tasksByDeskId
    }

    /**
     * Returns all preserved tasks for the preserved display regardless of what desk they appear in.
     */
    fun getPreservedTasks(uniqueDisplayId: String): List<Int> {
        val preservedDesks =
            preservedDisplaysByUniqueId[uniqueDisplayId]?.orderedDesks ?: emptySet()
        val preservedTasks = mutableListOf<Int>()
        for (desk in preservedDesks) {
            desk.freeformTasksInZOrder.forEach { taskId -> preservedTasks.add(taskId) }
        }
        return preservedTasks
    }

    /** Returns the active desk on the preserved display for the specified unique display id. */
    fun getPreservedActiveDesk(uniqueDisplayId: String): Int? =
        preservedDisplaysByUniqueId[uniqueDisplayId]?.activeDeskId
+28 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.wm.shell.desktopmode

import android.app.ActivityManager.RunningTaskInfo
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.view.Display
@@ -448,11 +449,38 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
        desktopState.overrideDesktopModeSupportPerDisplay[externalDisplayId] = true
        whenever(mockDesktopRepository.hasPreservedDisplayForUniqueDisplayId(UNIQUE_DISPLAY_ID))
            .thenReturn(true)
        val preservedFocusedTaskIds = listOf(1)
        whenever(mockDesktopRepository.getPreservedTasks(UNIQUE_DISPLAY_ID))
            .thenReturn(preservedFocusedTaskIds)
        whenever(mockDesktopTasksController.getFocusedNonDesktopTasks(any(), any()))
            .thenReturn(emptyList())

        onDisplaysChangedListenerCaptor.lastValue.onDesktopModeEligibleChanged(externalDisplayId)

        verify(mockDesktopTasksController)
            .restoreDisplay(eq(externalDisplayId), eq(UNIQUE_DISPLAY_ID), eq(PRIMARY_USER_ID))
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION)
    fun testDisplayReconnected_tasksFocusedOnDefaultDisplay_skipsReconnect() {
        desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = false
        desktopState.overrideDesktopModeSupportPerDisplay[externalDisplayId] = true
        whenever(mockDesktopRepository.hasPreservedDisplayForUniqueDisplayId(UNIQUE_DISPLAY_ID))
            .thenReturn(true)
        val preservedFocusedTaskIds = listOf(1)
        whenever(mockDesktopRepository.getPreservedTasks(UNIQUE_DISPLAY_ID))
            .thenReturn(preservedFocusedTaskIds)
        val task = RunningTaskInfo().apply { this.taskId = 1 }
        whenever(mockDesktopTasksController.getFocusedNonDesktopTasks(any(), any()))
            .thenReturn(listOf(task))

        addDisplay(2)

        verify(mockDesktopTasksController, never())
            .restoreDisplay(eq(externalDisplayId), eq(UNIQUE_DISPLAY_ID), eq(PRIMARY_USER_ID))
    }

    private fun addDisplay(displayId: Int, withTda: Boolean = false) {
        onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(displayId)
        if (withTda) {
+1 −50
Original line number Diff line number Diff line
@@ -10743,13 +10743,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
    )
    fun onDisplayDisconnect_desktopModeNotSupported_reparentsDeskTasks_focusedTaskToTop() {
    fun onDisplayDisconnect_desktopModeNotSupported_reparentsDeskTasks_focusedTaskToBottom() {
        val defaultDisplayTask = setUpFullscreenTask()
        val transition = Binder()
        taskRepository.addDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID)
        val secondDisplayTask = setUpFreeformTask(SECOND_DISPLAY)
        val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!

        val wct =
            performDisplayDisconnectTransition(
                transition = transition,
@@ -10758,54 +10757,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
                defaultDisplayTask = defaultDisplayTask,
                secondDisplayTask = secondDisplayTask,
            )

        assertThat(wct).isNotNull()
        wct.assertHop(
            ReparentPredicate(
                token = secondDisplayTask.token,
                parentToken = tda.token,
                toTop = true,
            )
        )
        verify(desksOrganizer).removeDesk(any(), eq(DISCONNECTED_DESK_ID), any())
        verify(desksTransitionsObserver)
            .addPendingTransition(
                argThat {
                    this is DeskTransition.RemoveDesk &&
                        this.token == transition &&
                        this.displayId == SECOND_DISPLAY &&
                        this.deskId == DISCONNECTED_DESK_ID
                }
            )
        verify(desksTransitionsObserver)
            .addPendingTransition(
                argThat {
                    this is DeskTransition.RemoveDisplay &&
                        this.token == transition &&
                        this.displayId == SECOND_DISPLAY
                }
            )
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
    )
    fun onDisplayDisconnect_desktopModeNotSupported_reparentsDeskTasks_nonFocusedTaskToBottom() {
        val defaultDisplayTask = setUpFullscreenTask()
        val transition = Binder()
        taskRepository.addDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID)
        val secondDisplayTask = setUpFreeformTask(SECOND_DISPLAY)
        val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
        val wct =
            performDisplayDisconnectTransition(
                transition = transition,
                desktopSupportedOnDefaultDisplay = false,
                taskOnSecondDisplayHasFocus = false,
                defaultDisplayTask = defaultDisplayTask,
                secondDisplayTask = secondDisplayTask,
            )
        wct.assertHop(
            ReparentPredicate(
                token = secondDisplayTask.token,