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

Commit 75ce70f8 authored by Yuichiro Hanada's avatar Yuichiro Hanada
Browse files

Change opacity of the drag indicator according to the cursor position

This CL makes the drag indicator change the opacity of itself if the
cursor is on a different display.

Bug: 383240504
Test: WMShellUnitTests
Test: go/cd-smoke
Flag: com.android.window.flags.enable_connected_displays_window_drag
Change-Id: I3e31ef55d78619e2851af75dbeb357495fce518d
parent 28144c43
Loading
Loading
Loading
Loading
+19 −8
Original line number Diff line number Diff line
@@ -36,16 +36,18 @@ class MultiDisplayDragMoveIndicatorController(
        mutableMapOf<Int, MutableMap<Int, MultiDisplayDragMoveIndicatorSurface>>()

    /**
     * Called during drag move, which started at [startDisplayId]. Updates the position and
     * visibility of the drag move indicators for the [taskInfo] based on [boundsDp] on the
     * destination displays ([displayIds]) as the dragged window moves. [transactionSupplier]
     * provides a [SurfaceControl.Transaction] for applying changes to the indicator surfaces.
     * Called during drag move, which started at [startDisplayId] and currently
     * at [currentDisplayid]. Updates the position and visibility of the drag move indicators
     * for the [taskInfo] based on [boundsDp] on the destination displays ([displayIds])
     * as the dragged window moves. [transactionSupplier] provides a [SurfaceControl.Transaction]
     * for applying changes to the indicator surfaces.
     *
     * It is executed on the [desktopExecutor] to prevent blocking the main thread and avoid jank,
     * as creating and manipulating surfaces can be expensive.
     */
    fun onDragMove(
        boundsDp: RectF,
        currentDisplayId: Int,
        startDisplayId: Int,
        taskInfo: RunningTaskInfo,
        displayIds: Set<Int>,
@@ -59,11 +61,19 @@ class MultiDisplayDragMoveIndicatorController(
                }
                val displayLayout = displayController.getDisplayLayout(displayId) ?: continue
                val displayContext = displayController.getDisplayContext(displayId) ?: continue
                val shouldBeVisible =
                    RectF.intersects(RectF(boundsDp), displayLayout.globalBoundsDp())
                val visibility =
                    if (RectF.intersects(RectF(boundsDp), displayLayout.globalBoundsDp())) {
                        if (displayId == currentDisplayId) {
                            MultiDisplayDragMoveIndicatorSurface.Visibility.VISIBLE
                        } else {
                            MultiDisplayDragMoveIndicatorSurface.Visibility.TRANSLUCENT
                        }
                    } else {
                        MultiDisplayDragMoveIndicatorSurface.Visibility.INVISIBLE
                    }
                if (
                    dragIndicators[taskInfo.taskId]?.containsKey(displayId) != true &&
                        !shouldBeVisible
                        visibility == MultiDisplayDragMoveIndicatorSurface.Visibility.INVISIBLE
                ) {
                    // Skip this display if:
                    // - It doesn't have an existing indicator that needs to be updated, AND
@@ -82,7 +92,7 @@ class MultiDisplayDragMoveIndicatorController(
                    dragIndicators.getOrPut(taskInfo.taskId) { mutableMapOf() }
                dragIndicatorsForTask[displayId]?.also { existingIndicator ->
                    val transaction = transactionSupplier()
                    existingIndicator.relayout(boundsPx, transaction, shouldBeVisible)
                    existingIndicator.relayout(boundsPx, transaction, visibility)
                    transaction.apply()
                } ?: run {
                    val newIndicator =
@@ -97,6 +107,7 @@ class MultiDisplayDragMoveIndicatorController(
                        rootTaskDisplayAreaOrganizer,
                        displayId,
                        boundsPx,
                        visibility,
                    )
                    dragIndicatorsForTask[displayId] = newIndicator
                }
+30 −7
Original line number Diff line number Diff line
@@ -70,7 +70,12 @@ class MultiDisplayDragMoveIndicatorSurface(
    surfaceControlViewHostFactory: WindowDecoration.SurfaceControlViewHostFactory =
        object : WindowDecoration.SurfaceControlViewHostFactory {},
) {
    private var isVisible = false
    public enum class Visibility {
        INVISIBLE,
        TRANSLUCENT,
        VISIBLE,
    }
    private var visibility = Visibility.INVISIBLE

    // A container surface to host the veil background
    private var veilSurface: SurfaceControl? = null
@@ -182,6 +187,7 @@ class MultiDisplayDragMoveIndicatorSurface(
        rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
        displayId: Int,
        bounds: Rect,
        visibility: Visibility,
    ) {
        val background = backgroundSurface
        val icon = iconSurface
@@ -204,10 +210,9 @@ class MultiDisplayDragMoveIndicatorSurface(
        }

        val backgroundColor = decorThemeUtil.getColorScheme(taskInfo).surfaceContainer
        isVisible = true

        rootTaskDisplayAreaOrganizer.reparentToDisplayArea(displayId, veil, transaction)
        relayout(bounds, transaction, shouldBeVisible = true)
        relayout(bounds, transaction, visibility)
        transaction
            .show(veil)
            .show(background)
@@ -221,14 +226,15 @@ class MultiDisplayDragMoveIndicatorSurface(

    /**
     * Repositions and resizes the indicator surface based on [bounds] using [transaction]. The
     * [shouldBeVisible] flag indicates whether the indicator is within the display after relayout.
     * [newVisibility] flag indicates whether the indicator is within the display after relayout.
     */
    fun relayout(bounds: Rect, transaction: SurfaceControl.Transaction, shouldBeVisible: Boolean) {
        if (!isVisible && !shouldBeVisible) {
    fun relayout(bounds: Rect, transaction: SurfaceControl.Transaction, newVisibility: Visibility) {
        if (visibility == Visibility.INVISIBLE && newVisibility == Visibility.INVISIBLE) {
            // No need to relayout if the surface is already invisible and should not be visible.
            return
        }
        isVisible = shouldBeVisible

        visibility = newVisibility
        val veil = veilSurface ?: return
        val icon = iconSurface ?: return
        val iconPosition = calculateAppIconPosition(bounds)
@@ -236,6 +242,20 @@ class MultiDisplayDragMoveIndicatorSurface(
            .setCrop(veil, bounds)
            .setCornerRadius(veil, cornerRadius)
            .setPosition(icon, iconPosition.x, iconPosition.y)
        when (visibility) {
            Visibility.VISIBLE ->
                transaction
                    .setAlpha(veil, ALPHA_FOR_MOVE_INDICATOR_ON_DISPLAY_WITH_CURSOR)
                    .setAlpha(icon, ALPHA_FOR_MOVE_INDICATOR_ON_DISPLAY_WITH_CURSOR)
            Visibility.TRANSLUCENT ->
                transaction
                    .setAlpha(veil, ALPHA_FOR_MOVE_INDICATOR_ON_NON_CURSOR_DISPLAY)
                    .setAlpha(icon, ALPHA_FOR_MOVE_INDICATOR_ON_NON_CURSOR_DISPLAY)
            Visibility.INVISIBLE -> {
                // Do nothing intentionally. Falling into this means the bounds is outside
                // of the display, so no need to hide the surface explicitly.
            }
        }
    }

    private fun calculateAppIconPosition(surfaceBounds: Rect): PointF {
@@ -291,5 +311,8 @@ class MultiDisplayDragMoveIndicatorSurface(
        private const val MOVE_INDICATOR_BACKGROUND_LAYER = 0
        /** Icon layer (drawn second/top, above the background). */
        private const val MOVE_INDICATOR_ICON_LAYER = 1

        private const val ALPHA_FOR_MOVE_INDICATOR_ON_DISPLAY_WITH_CURSOR = 1.0f
        private const val ALPHA_FOR_MOVE_INDICATOR_ON_NON_CURSOR_DISPLAY = 0.8f
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -210,6 +210,7 @@ class MultiDisplayVeiledResizeTaskPositioner(

                multiDisplayDragMoveIndicatorController.onDragMove(
                    boundsDp,
                    displayId,
                    startDisplayId,
                    desktopWindowDecoration.mTaskInfo,
                    displayIds,
+7 −2
Original line number Diff line number Diff line
@@ -97,6 +97,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
    fun onDrag_boundsNotIntersectWithDisplay_noIndicator() {
        controller.onDragMove(
            RectF(2000f, 2000f, 2100f, 2200f), // not intersect with any display
            currentDisplayId = 0,
            startDisplayId = 0,
            taskInfo,
            displayIds = setOf(0, 1),
@@ -110,6 +111,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
    fun onDrag_boundsIntersectWithStartDisplay_noIndicator() {
        controller.onDragMove(
            RectF(100f, 100f, 200f, 200f), // intersect with display 0
            currentDisplayId = 0,
            startDisplayId = 0,
            taskInfo,
            displayIds = setOf(0, 1),
@@ -123,6 +125,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
    fun onDrag_boundsIntersectWithNonStartDisplay_showAndDisposeIndicator() {
        controller.onDragMove(
            RectF(100f, -100f, 200f, 200f), // intersect with display 0 and 1
            currentDisplayId = 1,
            startDisplayId = 0,
            taskInfo,
            displayIds = setOf(0, 1),
@@ -131,10 +134,12 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {

        verify(indicatorSurfaceFactory, times(1)).create(eq(taskInfo), eq(display1), any())
        verify(indicatorSurface1, times(1))
            .show(transaction, taskInfo, rootTaskDisplayAreaOrganizer, 1, Rect(0, 1800, 200, 2400))
            .show(transaction, taskInfo, rootTaskDisplayAreaOrganizer, 1, Rect(0, 1800, 200, 2400),
                  MultiDisplayDragMoveIndicatorSurface.Visibility.VISIBLE)

        controller.onDragMove(
            RectF(2000f, 2000f, 2100f, 2200f), // not intersect with display 1
            currentDisplayId = 0,
            startDisplayId = 0,
            taskInfo,
            displayIds = setOf(0, 1)
@@ -144,7 +149,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
        }

        verify(indicatorSurface1, times(1))
            .relayout(any(), eq(transaction), shouldBeVisible = eq(false))
            .relayout(any(), eq(transaction), eq(MultiDisplayDragMoveIndicatorSurface.Visibility.INVISIBLE))

        controller.onDragEnd(TASK_ID, { transaction })
        while (executor.callbacks.isNotEmpty()) {
+52 −5
Original line number Diff line number Diff line
@@ -130,8 +130,10 @@ class MultiDisplayDragMoveIndicatorSurfaceTest : ShellTestCase() {
        whenever(mockTransaction.setCornerRadius(any(), any())).thenReturn(mockTransaction)
        whenever(mockTransaction.setCornerRadius(any(), any())).thenReturn(mockTransaction)
        whenever(mockTransaction.show(any())).thenReturn(mockTransaction)
        whenever(mockTransaction.hide(any())).thenReturn(mockTransaction)
        whenever(mockTransaction.setColor(any(), any())).thenReturn(mockTransaction)
        whenever(mockTransaction.setLayer(any(), any())).thenReturn(mockTransaction)
        whenever(mockTransaction.setAlpha(any(), any())).thenReturn(mockTransaction)

        dragIndicatorSurface =
            MultiDisplayDragMoveIndicatorSurface(
@@ -191,6 +193,7 @@ class MultiDisplayDragMoveIndicatorSurfaceTest : ShellTestCase() {
            mockRootTaskDisplayAreaOrganizer,
            DEFAULT_DISPLAY,
            BOUNDS,
            MultiDisplayDragMoveIndicatorSurface.Visibility.VISIBLE,
        )

        verify(mockRootTaskDisplayAreaOrganizer)
@@ -205,7 +208,7 @@ class MultiDisplayDragMoveIndicatorSurfaceTest : ShellTestCase() {
    }

    @Test
    fun relayout_whenVisibleAndShouldBeVisible_setsCropAndPosition() {
    fun relayout_whenVisibleAndDoesntChangeVisibility_setsCropAndPosition() {
        val expectedX = NEW_BOUNDS.left + NEW_BOUNDS.width().toFloat() / 2 - ICON_SIZE.toFloat() / 2
        val expectedY = NEW_BOUNDS.top + NEW_BOUNDS.height().toFloat() / 2 - ICON_SIZE.toFloat() / 2

@@ -215,10 +218,12 @@ class MultiDisplayDragMoveIndicatorSurfaceTest : ShellTestCase() {
            mockRootTaskDisplayAreaOrganizer,
            DEFAULT_DISPLAY,
            BOUNDS,
            MultiDisplayDragMoveIndicatorSurface.Visibility.VISIBLE
        )
        clearInvocations(mockTransaction)

        dragIndicatorSurface.relayout(NEW_BOUNDS, mockTransaction, shouldBeVisible = true)
        dragIndicatorSurface.relayout(NEW_BOUNDS, mockTransaction,
                                      MultiDisplayDragMoveIndicatorSurface.Visibility.VISIBLE)

        verify(mockTransaction).setCrop(eq(mockVeilSurface), eq(NEW_BOUNDS))
        verify(mockTransaction).setPosition(eq(mockIconSurface), eq(expectedX), eq(expectedY))
@@ -235,9 +240,11 @@ class MultiDisplayDragMoveIndicatorSurfaceTest : ShellTestCase() {
            mockRootTaskDisplayAreaOrganizer,
            DEFAULT_DISPLAY,
            BOUNDS,
            MultiDisplayDragMoveIndicatorSurface.Visibility.VISIBLE
        )
        clearInvocations(mockTransaction)
        dragIndicatorSurface.relayout(NEW_BOUNDS, mockTransaction, shouldBeVisible = false)
        dragIndicatorSurface.relayout(NEW_BOUNDS, mockTransaction,
                                      MultiDisplayDragMoveIndicatorSurface.Visibility.INVISIBLE)

        verify(mockTransaction).setCrop(eq(mockVeilSurface), eq(NEW_BOUNDS))
        verify(mockTransaction).setPosition(eq(mockIconSurface), eq(expectedX), eq(expectedY))
@@ -248,7 +255,8 @@ class MultiDisplayDragMoveIndicatorSurfaceTest : ShellTestCase() {
        val expectedX = NEW_BOUNDS.left + NEW_BOUNDS.width().toFloat() / 2 - ICON_SIZE.toFloat() / 2
        val expectedY = NEW_BOUNDS.top + NEW_BOUNDS.height().toFloat() / 2 - ICON_SIZE.toFloat() / 2

        dragIndicatorSurface.relayout(NEW_BOUNDS, mockTransaction, shouldBeVisible = true)
        dragIndicatorSurface.relayout(NEW_BOUNDS, mockTransaction,
                                      MultiDisplayDragMoveIndicatorSurface.Visibility.VISIBLE)

        verify(mockTransaction).setCrop(eq(mockVeilSurface), eq(NEW_BOUNDS))
        verify(mockTransaction).setPosition(eq(mockIconSurface), eq(expectedX), eq(expectedY))
@@ -256,16 +264,55 @@ class MultiDisplayDragMoveIndicatorSurfaceTest : ShellTestCase() {

    @Test
    fun relayout_whenInvisibleAndShouldBeInvisible_doesNotSetCropOrPosition() {
        dragIndicatorSurface.relayout(NEW_BOUNDS, mockTransaction, shouldBeVisible = false)
        dragIndicatorSurface.relayout(NEW_BOUNDS, mockTransaction,
                                      MultiDisplayDragMoveIndicatorSurface.Visibility.INVISIBLE)

        verify(mockTransaction, never()).setCrop(any(), any())
        verify(mockTransaction, never()).setPosition(any(), any(), any())
    }

    @Test
    fun relayout_whenVisibleAndShouldBeTranslucent_setAlpha() {
        dragIndicatorSurface.show(
            mockTransaction,
            taskInfo,
            mockRootTaskDisplayAreaOrganizer,
            DEFAULT_DISPLAY,
            BOUNDS,
            MultiDisplayDragMoveIndicatorSurface.Visibility.VISIBLE
        )
        clearInvocations(mockTransaction)
        dragIndicatorSurface.relayout(NEW_BOUNDS, mockTransaction,
                                      MultiDisplayDragMoveIndicatorSurface.Visibility.TRANSLUCENT)

        verify(mockTransaction).setAlpha(eq(mockVeilSurface), eq(ALPHA_FOR_TRANSLUCENT))
        verify(mockTransaction).setAlpha(eq(mockIconSurface), eq(ALPHA_FOR_TRANSLUCENT))
    }

    @Test
    fun relayout_whenTranslucentAndShouldBeVisible_setAlpha() {
        dragIndicatorSurface.show(
            mockTransaction,
            taskInfo,
            mockRootTaskDisplayAreaOrganizer,
            DEFAULT_DISPLAY,
            BOUNDS,
            MultiDisplayDragMoveIndicatorSurface.Visibility.TRANSLUCENT
        )
        clearInvocations(mockTransaction)
        dragIndicatorSurface.relayout(NEW_BOUNDS, mockTransaction,
                                      MultiDisplayDragMoveIndicatorSurface.Visibility.VISIBLE)

        verify(mockTransaction).setAlpha(eq(mockVeilSurface), eq(ALPHA_FOR_VISIBLE))
        verify(mockTransaction).setAlpha(eq(mockIconSurface), eq(ALPHA_FOR_VISIBLE))
    }

    companion object {
        private const val CORNER_RADIUS = 32
        private const val ICON_SIZE = 48
        private val BOUNDS = Rect(10, 20, 100, 200)
        private val NEW_BOUNDS = Rect(50, 50, 150, 250)
        private val ALPHA_FOR_TRANSLUCENT = 0.8f
        private val ALPHA_FOR_VISIBLE = 1.0f
    }
}