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

Commit 5c46083e authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Projected mode: Do not alter internal display tasks on disconnect/reconnect." into main

parents 40f4c2be 762934bc
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,