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

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

Suppress visual indicator if cursor leaves original display

Addresses an issue where the desktop mode visual indicator (e.g., for
window snapping) would appear on the wrong monitor during cross-display
drag operations.

The root cause was that the indicator's visibility was tied solely to
the task's origin display, ignoring the cursor's actual current display.
This meant dragging a window to display B and hovering near an edge
could incorrectly trigger the indicator animation on display A where the
window was belonging to. See video: http://go/visual-indicator-xdisplay

As an initial fix, this commit prevents the indicator from showing at
all if the cursor is not on the display where the drag started. This
deliminates the confusing display on the incorrect monitor.

Bug: 389868684
Test: manual; atest DesktopTasksControllerTest DesktopModeVisualIndicatorTest
Flag: com.android.window.flags.enable_connected_displays_window_drag
Change-Id: Ifbf022ce6314fe700953285cc54b43da9adf6226
parent 35d98993
Loading
Loading
Loading
Loading
+10 −3
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.graphics.Region;
import android.util.Pair;
import android.view.Display;
import android.view.SurfaceControl;
import android.window.DesktopExperienceFlags;
import android.window.DesktopModeFlags;

import androidx.annotation.VisibleForTesting;
@@ -258,8 +259,8 @@ public class DesktopModeVisualIndicator {
     * display, including no visible indicator, and update the indicator.
     */
    @NonNull
    IndicatorType updateIndicatorType(PointF inputCoordinates) {
        final IndicatorType result = calculateIndicatorType(inputCoordinates);
    IndicatorType updateIndicatorType(int displayId, PointF inputCoordinates) {
        final IndicatorType result = calculateIndicatorType(displayId, inputCoordinates);
        updateIndicatorWithType(result);
        return result;
    }
@@ -269,7 +270,13 @@ public class DesktopModeVisualIndicator {
     * display, including no visible indicator.
     */
    @NonNull
    IndicatorType calculateIndicatorType(PointF inputCoordinates) {
    IndicatorType calculateIndicatorType(int displayId, PointF inputCoordinates) {
        if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG.isTrue()
                && mTaskInfo.displayId != displayId) {
            // TODO(b/411292927): Allow indicator to show on the target display (`displayId`)
            // even if it differs from the task's original display.
            return NO_INDICATOR;
        }
        final IndicatorType result;
        if (mUseSmallTabletRegions) {
            result = getIndicatorSmallTablet(inputCoordinates);
+15 −4
Original line number Diff line number Diff line
@@ -3731,12 +3731,15 @@ class DesktopTasksController(
     *
     * @param taskInfo the task being dragged.
     * @param taskSurface SurfaceControl of dragged task.
     * @param displayId displayId of the input event.
     * @param inputX x coordinate of input. Used for checks against left/right edge of screen.
     * @param inputY y coordinate of input. Used for checks about cross display drag.
     * @param taskBounds bounds of dragged task. Used for checks against status bar height.
     */
    fun onDragPositioningMove(
        taskInfo: RunningTaskInfo,
        taskSurface: SurfaceControl,
        displayId: Int,
        inputX: Float,
        inputY: Float,
        taskBounds: Rect,
@@ -3747,6 +3750,7 @@ class DesktopTasksController(
            updateVisualIndicator(
                taskInfo,
                taskSurface,
                displayId,
                inputX,
                taskBounds.top.toFloat(),
                DragStartState.FROM_FREEFORM,
@@ -3757,7 +3761,7 @@ class DesktopTasksController(
        val indicator =
            getOrCreateVisualIndicator(taskInfo, taskSurface, DragStartState.FROM_FREEFORM)
        val indicatorType =
            indicator.calculateIndicatorType(PointF(inputX, taskBounds.top.toFloat()))
            indicator.calculateIndicatorType(displayId, PointF(inputX, taskBounds.top.toFloat()))
        visualIndicatorUpdateScheduler.schedule(
            taskInfo.displayId,
            indicatorType,
@@ -3771,12 +3775,13 @@ class DesktopTasksController(
    fun updateVisualIndicator(
        taskInfo: RunningTaskInfo,
        taskSurface: SurfaceControl?,
        displayId: Int,
        inputX: Float,
        taskTop: Float,
        dragStartState: DragStartState,
    ): IndicatorType {
        return getOrCreateVisualIndicator(taskInfo, taskSurface, dragStartState)
            .updateIndicatorType(PointF(inputX, taskTop))
            .updateIndicatorType(displayId, PointF(inputX, taskTop))
    }

    @VisibleForTesting
@@ -3821,6 +3826,7 @@ class DesktopTasksController(
     *
     * @param taskInfo the task being dragged.
     * @param taskSurface the leash of the task being dragged.
     * @param displayId the displayId of the input event.
     * @param inputCoordinate the coordinates of the motion event
     * @param currentDragBounds the current bounds of where the visible task is (might be actual
     *   task bounds or just task leash)
@@ -3830,6 +3836,7 @@ class DesktopTasksController(
    fun onDragPositioningEnd(
        taskInfo: RunningTaskInfo,
        taskSurface: SurfaceControl,
        displayId: Int,
        inputCoordinate: PointF,
        currentDragBounds: Rect,
        validDragArea: Rect,
@@ -3843,7 +3850,8 @@ class DesktopTasksController(
        val indicator = getVisualIndicator() ?: return
        val indicatorType =
            indicator.updateIndicatorType(
                PointF(inputCoordinate.x, currentDragBounds.top.toFloat())
                displayId,
                PointF(inputCoordinate.x, currentDragBounds.top.toFloat()),
            )
        when (indicatorType) {
            IndicatorType.TO_FULLSCREEN_INDICATOR -> {
@@ -3973,11 +3981,13 @@ class DesktopTasksController(
    /**
     * Perform checks required when drag ends under status bar area.
     *
     * @param displayId the displayId of the input event.
     * @param taskInfo the task being dragged.
     * @param y height of drag, to be checked against status bar height.
     * @return the [IndicatorType] used for the resulting transition
     */
    fun onDragPositioningEndThroughStatusBar(
        displayId: Int,
        inputCoordinates: PointF,
        taskInfo: RunningTaskInfo,
        taskSurface: SurfaceControl,
@@ -3985,7 +3995,7 @@ class DesktopTasksController(
        // End the drag_hold CUJ interaction.
        interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
        val indicator = getVisualIndicator() ?: return IndicatorType.NO_INDICATOR
        val indicatorType = indicator.updateIndicatorType(inputCoordinates)
        val indicatorType = indicator.updateIndicatorType(displayId, inputCoordinates)
        when (indicatorType) {
            IndicatorType.TO_DESKTOP_INDICATOR -> {
                latencyTracker.onActionStart(
@@ -4097,6 +4107,7 @@ class DesktopTasksController(
            updateVisualIndicator(
                taskInfo,
                dragEvent.dragSurface,
                dragEvent.displayId,
                dragEvent.x,
                dragEvent.y,
                DragStartState.DRAGGED_INTENT,
+7 −4
Original line number Diff line number Diff line
@@ -1362,6 +1362,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                            e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                    mDesktopTasksController.onDragPositioningMove(taskInfo,
                            decoration.mTaskSurface,
                            e.getDisplayId(),
                            e.getRawX(dragPointerIdx),
                            e.getRawY(dragPointerIdx),
                            newTaskBounds);
@@ -1391,6 +1392,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                    // or transforming to fullscreen) before setting new task bounds.
                    mDesktopTasksController.onDragPositioningEnd(
                            taskInfo, decoration.mTaskSurface,
                            e.getDisplayId(),
                            new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
                            newTaskBounds, decoration.calculateValidDragArea(),
                            new Rect(mOnDragStartInitialBounds), e);
@@ -1628,8 +1630,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                                    .getDragStartState(relevantDecor.mTaskInfo);
                    if (dragStartState == null) return;
                    mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo,
                            relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY(),
                            dragStartState);
                            relevantDecor.mTaskSurface, ev.getDisplayId(), ev.getRawX(),
                            ev.getRawY(), dragStartState);
                    mTransitionDragActive = false;
                    if (mMoveToDesktopAnimator != null) {
                        // Though this isn't a hover event, we need to update handle's hover state
@@ -1674,8 +1676,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                    if (dragStartState == null) return;
                    final DesktopModeVisualIndicator.IndicatorType indicatorType =
                            mDesktopTasksController.updateVisualIndicator(
                                    relevantDecor.mTaskInfo,
                                    relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY(),
                                    relevantDecor.mTaskInfo, relevantDecor.mTaskSurface,
                                    ev.getDisplayId(), ev.getRawX(), ev.getRawY(),
                                    dragStartState);
                    if (indicatorType != TO_FULLSCREEN_INDICATOR
                            || BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
@@ -1708,6 +1710,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
    private void endDragToDesktop(MotionEvent ev, DesktopModeWindowDecoration relevantDecor) {
        DesktopModeVisualIndicator.IndicatorType resultType =
                mDesktopTasksController.onDragPositioningEndThroughStatusBar(
                        ev.getDisplayId(),
                        new PointF(ev.getRawX(), ev.getRawY()),
                        relevantDecor.mTaskInfo,
                        relevantDecor.mTaskSurface);
+45 −30
Original line number Diff line number Diff line
@@ -24,9 +24,11 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
import com.android.internal.policy.SystemBarUtils
import com.android.window.flags.Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.window.flags.Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX
import com.android.wm.shell.R
@@ -263,18 +265,18 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
    @Test
    fun testDefaultIndicators() {
        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
        var result = visualIndicator.updateIndicatorType(PointF(-10000f, 500f))
        var result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(-10000f, 500f))
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
        result = visualIndicator.updateIndicatorType(PointF(10000f, 500f))
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(10000f, 500f))
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
        result = visualIndicator.updateIndicatorType(PointF(500f, 10000f))
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(500f, 10000f))
        assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
        result = visualIndicator.updateIndicatorType(PointF(500f, 10000f))
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(500f, 10000f))
        assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
    }

@@ -285,28 +287,28 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
    )
    fun testDefaultIndicators_enableBubbleToFullscreen() {
        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
        var result = visualIndicator.updateIndicatorType(PointF(10f, 1500f))
        var result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(10f, 1500f))
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_LEFT_INDICATOR)
        result = visualIndicator.updateIndicatorType(PointF(2390f, 1500f))
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(2390f, 1500f))
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR)

        // Check that bubble zones are not available from split
        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
        result = visualIndicator.updateIndicatorType(PointF(10f, 1500f))
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(10f, 1500f))
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
        result = visualIndicator.updateIndicatorType(PointF(2390f, 1500f))
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(2390f, 1500f))
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)

        // Check that bubble zones are not available from desktop
        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
        result = visualIndicator.updateIndicatorType(PointF(10f, 1500f))
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(10f, 1500f))
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
        result = visualIndicator.updateIndicatorType(PointF(2390f, 1500f))
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(2390f, 1500f))
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
    }
@@ -324,23 +326,23 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
            isSmallTablet = true,
            isLeftRightSplit = true,
        )
        var result = visualIndicator.updateIndicatorType(foldCenter())
        var result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldCenter())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)

        result = visualIndicator.updateIndicatorType(foldLeftEdge())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftEdge())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)

        result = visualIndicator.updateIndicatorType(foldRightEdge())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldRightEdge())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)

        result = visualIndicator.updateIndicatorType(foldLeftBottom())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftBottom())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_LEFT_INDICATOR)

        result = visualIndicator.updateIndicatorType(foldRightBottom())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldRightBottom())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR)
    }
@@ -359,16 +361,16 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
            isSmallTablet = true,
            isLeftRightSplit = true,
        )
        var result = visualIndicator.updateIndicatorType(foldCenter())
        var result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldCenter())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)

        // Check that bubbles are not available from split
        result = visualIndicator.updateIndicatorType(foldLeftBottom())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftBottom())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)

        result = visualIndicator.updateIndicatorType(foldRightBottom())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldRightBottom())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
    }
@@ -383,15 +385,15 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
            isSmallTablet = true,
            isLeftRightSplit = true,
        )
        var result = visualIndicator.updateIndicatorType(foldCenter())
        var result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldCenter())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)

        result = visualIndicator.updateIndicatorType(foldLeftBottom())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftBottom())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_LEFT_INDICATOR)

        result = visualIndicator.updateIndicatorType(foldRightBottom())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldRightBottom())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR)
    }
@@ -409,19 +411,19 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
            isSmallTablet = true,
            isLeftRightSplit = false,
        )
        var result = visualIndicator.updateIndicatorType(foldCenter())
        var result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldCenter())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)

        result = visualIndicator.updateIndicatorType(foldLeftEdge())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftEdge())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)

        result = visualIndicator.updateIndicatorType(foldLeftBottom())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftBottom())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_LEFT_INDICATOR)

        result = visualIndicator.updateIndicatorType(foldRightBottom())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldRightBottom())
        assertThat(result)
            .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR)

@@ -431,11 +433,24 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
            isLeftRightSplit = false,
        )
        // No indicator as top/bottom split apps should not be dragged
        result = visualIndicator.updateIndicatorType(foldCenter())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldCenter())
        assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
        result = visualIndicator.updateIndicatorType(foldLeftBottom())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftBottom())
        assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
        result = visualIndicator.updateIndicatorType(foldRightBottom())
        result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldRightBottom())
        assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
    }

    @Test
    @EnableFlags(FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
    fun testDefaultIndicators_crossDisplayDrag_noIndicator() {
        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)

        // Simulate dragging to a point on a different display.
        // Even though this point (-10000f, 500f) would trigger TO_SPLIT_LEFT_INDICATOR
        // on the original display, dragging across displays should show no indicator.
        val result = visualIndicator.updateIndicatorType(/* displayId= */ 10, PointF(-10000f, 500f))

        assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
    }

@@ -451,7 +466,7 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
        desktopExecutor.flushAll()
        mainExecutor.flushAll()
        visualIndicator.updateIndicatorType(PointF(100f, 1500f))
        visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(100f, 1500f))
        desktopExecutor.flushAll()
        mainExecutor.flushAll()

@@ -472,7 +487,7 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
        desktopExecutor.flushAll()
        mainExecutor.flushAll()
        visualIndicator.updateIndicatorType(PointF(2300f, 1500f))
        visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(2300f, 1500f))
        desktopExecutor.flushAll()
        mainExecutor.flushAll()

+182 −45

File changed.

Preview size limit exceeded, changes collapsed.