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

Commit e6aadd7f authored by Qijing Yao's avatar Qijing Yao
Browse files

Update cross-display drag reparenting for multi-desks

Update cross-display drag handling to work correctly with the multi-desk
architecture. The previous direct `WindowContainerTransaction`
manipulation is incompatible with multi-desks; this change uses
`moveToDisplay` to reparent windows correctly, using the provided bounds
and transition handler.

Bug: 393969734
Test: atest; manual tests, following multi-desks test instuction
Flag: com.android.window.flags.enable_connected_displays_window_drag
Change-Id: I5380df5856b297d709b7481a39d8e7a5fd7a2605
parent dd8f4e6b
Loading
Loading
Loading
Loading
+31 −18
Original line number Original line Diff line number Diff line
@@ -150,6 +150,7 @@ import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener
import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener
@@ -1226,13 +1227,20 @@ class DesktopTasksController(
    }
    }


    /**
    /**
     * Move [task] to display with [displayId].
     * Move [task] to display with [displayId]. When [bounds] is not null, it will be used as the
     * bounds on the new display. When [transitionHandler] is not null, it will be used instead of
     * the default [DesktopModeMoveToDisplayTransitionHandler].
     *
     *
     * No-op if task is already on that display per [RunningTaskInfo.displayId].
     * No-op if task is already on that display per [RunningTaskInfo.displayId].
     *
     *
     * TODO: b/399411604 - split this up into smaller functions.
     * TODO: b/399411604 - split this up into smaller functions.
     */
     */
    private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
    private fun moveToDisplay(
        task: RunningTaskInfo,
        displayId: Int,
        bounds: Rect? = null,
        transitionHandler: TransitionHandler? = null,
    ) {
        logV("moveToDisplay: taskId=%d displayId=%d", task.taskId, displayId)
        logV("moveToDisplay: taskId=%d displayId=%d", task.taskId, displayId)
        if (task.displayId == displayId) {
        if (task.displayId == displayId) {
            logD("moveToDisplay: task already on display %d", displayId)
            logD("moveToDisplay: task already on display %d", displayId)
@@ -1264,7 +1272,9 @@ class DesktopTasksController(
            if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
                desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task)
                desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task)
            }
            }
            if (Flags.enableMoveToNextDisplayShortcut()) {
            if (bounds != null) {
                wct.setBounds(task.token, bounds)
            } else if (Flags.enableMoveToNextDisplayShortcut()) {
                applyFreeformDisplayChange(wct, task, displayId)
                applyFreeformDisplayChange(wct, task, displayId)
            }
            }
        }
        }
@@ -1305,7 +1315,11 @@ class DesktopTasksController(
                null
                null
            }
            }
        val transition =
        val transition =
            transitions.startTransition(TRANSIT_CHANGE, wct, moveToDisplayTransitionHandler)
            transitions.startTransition(
                TRANSIT_CHANGE,
                wct,
                transitionHandler ?: moveToDisplayTransitionHandler,
            )
        deactivationRunnable?.invoke(transition)
        deactivationRunnable?.invoke(transition)
        activationRunnable?.invoke(transition)
        activationRunnable?.invoke(transition)
    }
    }
@@ -3288,29 +3302,28 @@ class DesktopTasksController(
                    return
                    return
                }
                }


                // Update task bounds so that the task position will match the position of its leash
                val wct = WindowContainerTransaction()
                wct.setBounds(taskInfo.token, destinationBounds)

                val newDisplayId = motionEvent.getDisplayId()
                val newDisplayId = motionEvent.getDisplayId()
                val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(newDisplayId)
                val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(newDisplayId)
                val isCrossDisplayDrag =
                val isCrossDisplayDrag =
                    Flags.enableConnectedDisplaysWindowDrag() &&
                    Flags.enableConnectedDisplaysWindowDrag() &&
                        newDisplayId != taskInfo.getDisplayId() &&
                        newDisplayId != taskInfo.getDisplayId() &&
                        displayAreaInfo != null
                        displayAreaInfo != null
                val handler =

                if (isCrossDisplayDrag) {
                if (isCrossDisplayDrag) {
                        dragToDisplayTransitionHandler
                    moveToDisplay(
                        taskInfo,
                        newDisplayId,
                        destinationBounds,
                        dragToDisplayTransitionHandler,
                    )
                } else {
                } else {
                        null
                    // Update task bounds so that the task position will match the position of its
                    }
                    // leash
                if (isCrossDisplayDrag) {
                    val wct = WindowContainerTransaction()
                    // TODO: b/362720497 - reparent to a specific desk within the target display.
                    wct.setBounds(taskInfo.token, destinationBounds)
                    wct.reparent(taskInfo.token, displayAreaInfo.token, /* onTop= */ true)
                    transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
                }
                }


                transitions.startTransition(TRANSIT_CHANGE, wct, handler)

                releaseVisualIndicator()
                releaseVisualIndicator()
            }
            }
            IndicatorType.TO_DESKTOP_INDICATOR -> {
            IndicatorType.TO_DESKTOP_INDICATOR -> {
+36 −0
Original line number Original line Diff line number Diff line
@@ -5968,6 +5968,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()


    @Test
    @Test
    @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
    @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun onDesktopDragEnd_noIndicatorAndMoveToNewDisplay_reparent() {
    fun onDesktopDragEnd_noIndicatorAndMoveToNewDisplay_reparent() {
        val task = setUpFreeformTask()
        val task = setUpFreeformTask()
        val spyController = spy(controller)
        val spyController = spy(controller)
@@ -6003,6 +6004,41 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
            )
            )
    }
    }


    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
    )
    fun onDesktopDragEnd_noIndicatorAndMoveToNewDisplay_reparentToDesk() {
        val deskId = 5
        val task = setUpFreeformTask()
        val spyController = spy(controller)
        val mockSurface = mock(SurfaceControl::class.java)
        val mockDisplayLayout = mock(DisplayLayout::class.java)
        taskRepository.addDesk(displayId = SECONDARY_DISPLAY_ID, deskId = deskId)
        whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
        whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
        spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))

        val currentDragBounds = Rect(100, 200, 500, 1000)
        whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
        whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
            .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
        whenever(motionEvent.displayId).thenReturn(SECONDARY_DISPLAY_ID)

        spyController.onDragPositioningEnd(
            task,
            mockSurface,
            inputCoordinate = PointF(200f, 300f),
            currentDragBounds,
            validDragArea = Rect(0, 50, 2000, 2000),
            dragStartBounds = Rect(),
            motionEvent,
        )

        verify(desksOrganizer).moveTaskToDesk(any(), eq(deskId), eq(task))
    }

    @Test
    @Test
    fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() {
    fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() {
        val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))
        val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))