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

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

Reapply "Correct opacitiy for drag move indicator"

This reapplies Ibe86db0aaa6ab20ac76a27f8db9dbf09c524ffd3.

When dragging a task between displays, the original task surface is
moved off-screen, and a mirrored surface is used as the drag indicator.
This is necessary because a mirrored surface cannot have a higher alpha
value than its source, and this approach allows for independent opacity
control of the indicators on different displays.

The key difference in this re-applied version is that the new behavior
is controlled by the feature flag ENABLE_WINDOW_DROP_SMOOTH_TRANSITION,
and will be merged with the change that defers drag indicator removal
until the window drop transition ends. Together, they should create a
smooth and seamless window drop experience.

Flag: com.android.window.flags.enable_window_drop_smooth_transition
Bug: 398992368
Test: manual and atest
Change-Id: Ic57fbd033fdbbd7a0b6394f467247ea2c053f61f
parent 9160446e
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -56,11 +56,11 @@ class MultiDisplayDragMoveIndicatorController(
        val transaction = transactionSupplier()
        for (displayId in displayIds) {
            if (
                displayId == startDisplayId ||
                (displayId == startDisplayId &&
                    !DesktopExperienceFlags.ENABLE_WINDOW_DROP_SMOOTH_TRANSITION.isTrue) ||
                    !desktopState.isDesktopModeSupportedOnDisplay(displayId)
            ) {
                // No need to render indicators on the original display where the drag started,
                // or on displays that do not support desktop mode.
                // No need to render indicators on displays that do not support desktop mode.
                continue
            }
            val displayLayout = displayController.getDisplayLayout(displayId) ?: continue
+31 −13
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ class MultiDisplayVeiledResizeTaskPositioner(
    @Surface.Rotation private var rotation = 0
    private var startDisplayId = 0
    private val displayIds = mutableSetOf<Int>()
    private var hasMovedTaskSurfaceOffScreen = false

    constructor(
        taskOrganizer: ShellTaskOrganizer,
@@ -106,6 +107,7 @@ class MultiDisplayVeiledResizeTaskPositioner(
    override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect {
        this.ctrlType = ctrlType
        startDisplayId = displayId
        hasMovedTaskSurfaceOffScreen = false
        taskBoundsAtDragStart.set(
            windowDecoration.taskInfo.configuration.windowConfiguration.bounds
        )
@@ -201,13 +203,27 @@ class MultiDisplayVeiledResizeTaskPositioner(
                    displayIds,
                    transactionSupplier,
                )

                if (DesktopExperienceFlags.ENABLE_WINDOW_DROP_SMOOTH_TRANSITION.isTrue) {
                    // Move the original task surface off-screen to hide it. A mirrored surface is
                    // used for the drag indicator on all displays, including the start display.
                    // This is necessary for independent opacity control, as a mirror's alpha is
                    // capped by its source.
                    if (!hasMovedTaskSurfaceOffScreen) {
                        hasMovedTaskSurfaceOffScreen = true
                        t.setPosition(
                            windowDecoration.taskSurface,
                            startDisplayLayout.width().toFloat(),
                            startDisplayLayout.height().toFloat(),
                        )
                    }
                } else {
                    t.setPosition(
                        windowDecoration.taskSurface,
                        repositionTaskBounds.left.toFloat(),
                        repositionTaskBounds.top.toFloat(),
                    )
                // Make the window translucent in the case when the cursor moves to another display.
                    // Make the window translucent in the case when the cursor moves to another
                    // display.
                    val alpha =
                        if (startDisplayId == displayId) {
                            ALPHA_FOR_WINDOW_ON_DISPLAY_WITH_CURSOR
@@ -216,6 +232,7 @@ class MultiDisplayVeiledResizeTaskPositioner(
                        }
                    t.setAlpha(windowDecoration.taskSurface, alpha)
                }
            }
            t.setFrameTimeline(Choreographer.getInstance().vsyncId)
            t.apply()
        }
@@ -300,6 +317,7 @@ class MultiDisplayVeiledResizeTaskPositioner(
        ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED
        taskBoundsAtDragStart.setEmpty()
        repositionStartPoint[0f] = 0f
        hasMovedTaskSurfaceOffScreen = false
        return Rect(repositionTaskBounds)
    }

+16 −2
Original line number Diff line number Diff line
@@ -19,10 +19,12 @@ import android.app.ActivityManager.RunningTaskInfo
import android.content.res.Configuration
import android.graphics.Rect
import android.graphics.RectF
import android.platform.test.annotations.EnableFlags
import android.testing.TestableResources
import android.view.SurfaceControl
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.MultiDisplayTestUtil.TestDisplay
@@ -85,6 +87,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
        taskInfo.taskId = TASK_ID
        whenever(displayController.getDisplayLayout(0)).thenReturn(spyDisplayLayout0)
        whenever(displayController.getDisplayLayout(1)).thenReturn(spyDisplayLayout1)
        whenever(displayController.getDisplayContext(0)).thenReturn(mContext)
        whenever(displayController.getDisplayContext(1)).thenReturn(mContext)
        whenever(indicatorSurfaceFactory.create(eq(mContext), eq(taskLeash)))
            .thenReturn(indicatorSurface)
@@ -109,7 +112,8 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
    }

    @Test
    fun onDrag_boundsIntersectWithStartDisplay_noIndicator() {
    @EnableFlags(Flags.FLAG_ENABLE_WINDOW_DROP_SMOOTH_TRANSITION)
    fun onDrag_boundsIntersectWithStartDisplay_showIndicator() {
        controller.onDragMove(
            RectF(100f, 100f, 200f, 200f), // intersect with display 0
            currentDisplayId = 0,
@@ -121,7 +125,17 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() {
            transaction
        }

        verify(indicatorSurfaceFactory, never()).create(any(), any())
        verify(indicatorSurfaceFactory, times(1)).create(eq(mContext), eq(taskLeash))
        verify(indicatorSurface, times(1))
            .show(
                transaction,
                taskInfo,
                rootTaskDisplayAreaOrganizer,
                0,
                Rect(100, 100, 200, 200),
                MultiDisplayDragMoveIndicatorSurface.Visibility.VISIBLE,
                1.0f,
            )
    }

    @Test
+105 −30
Original line number Diff line number Diff line
@@ -59,9 +59,9 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.eq
import org.mockito.kotlin.inOrder
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
@@ -222,10 +222,6 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
            STARTING_BOUNDS.left.toFloat() + 60,
            STARTING_BOUNDS.top.toFloat() + 100,
        )
        val rectAfterMove = Rect(STARTING_BOUNDS)
        rectAfterMove.offset(60, 100)
        verify(mockTransaction)
            .setPosition(any(), eq(rectAfterMove.left.toFloat()), eq(rectAfterMove.top.toFloat()))

        val endBounds =
            taskPositioner.onDragPositioningEnd(
@@ -242,6 +238,41 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
        Assert.assertEquals(rectAfterEnd, endBounds)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_WINDOW_DROP_SMOOTH_TRANSITION)
    fun testDragResize_movesTask_movesTaskSurfaceOffscreen() = runOnUiThread {
        whenever(spyDisplayLayout0.width()).thenReturn(DISPLAY_BOUNDS.width())
        whenever(spyDisplayLayout0.height()).thenReturn(DISPLAY_BOUNDS.height())

        taskPositioner.onDragPositioningStart(
            CTRL_TYPE_UNDEFINED,
            DISPLAY_ID_0,
            STARTING_BOUNDS.left.toFloat(),
            STARTING_BOUNDS.top.toFloat(),
        )

        taskPositioner.onDragPositioningMove(
            DISPLAY_ID_0,
            STARTING_BOUNDS.left.toFloat() + 60,
            STARTING_BOUNDS.top.toFloat() + 100,
        )

        val leftAfterMoveCaptor = argumentCaptor<Float>()
        val topAfterMoveCaptor = argumentCaptor<Float>()
        verify(mockTransaction)
            .setPosition(
                eq(mockSurfaceControl),
                leftAfterMoveCaptor.capture(),
                topAfterMoveCaptor.capture(),
            )
        val rectAfterMove = Rect(STARTING_BOUNDS)
        rectAfterMove.offsetTo(
            leftAfterMoveCaptor.firstValue.toInt(),
            topAfterMoveCaptor.firstValue.toInt(),
        )
        Assert.assertFalse(DISPLAY_BOUNDS.intersect(rectAfterMove))
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_WINDOW_DROP_SMOOTH_TRANSITION)
    fun testDragResize_movesTaskOnSameDisplay_noPxDpConversion() = runOnUiThread {
@@ -275,12 +306,6 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {

        taskPositioner.onDragPositioningMove(DISPLAY_ID_1, 200f, 1900f)

        val rectAfterMove = Rect(200, -50, 300, 50)
        verify(mockTransaction)
            .setPosition(any(), eq(rectAfterMove.left.toFloat()), eq(rectAfterMove.top.toFloat()))
        verify(mockTransaction)
            .setAlpha(eq(mockWindowDecoration.taskSurface), eq(ALPHA_FOR_TRANSLUCENT_WINDOW))

        val endBounds = taskPositioner.onDragPositioningEnd(DISPLAY_ID_1, 300f, 450f)
        val rectAfterEnd = Rect(300, 450, 500, 650)

@@ -292,7 +317,10 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_WINDOW_DROP_SMOOTH_TRANSITION)
    fun testDragResize_movesTaskToNewDisplayThenBackToOriginalDisplay() = runOnUiThread {
    fun testDragResize_movesTaskToNewDisplay_movesTaskSurfaceOffscreen() = runOnUiThread {
        whenever(spyDisplayLayout0.width()).thenReturn(DISPLAY_BOUNDS.width())
        whenever(spyDisplayLayout0.height()).thenReturn(DISPLAY_BOUNDS.height())

        taskPositioner.onDragPositioningStart(
            CTRL_TYPE_UNDEFINED,
            DISPLAY_ID_0,
@@ -300,31 +328,43 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
            STARTING_BOUNDS.top.toFloat(),
        )

        val inOrder = inOrder(mockTransaction)
        taskPositioner.onDragPositioningMove(DISPLAY_ID_1, 200f, 1900f)

        val leftAfterMoveCaptor = argumentCaptor<Float>()
        val topAfterMoveCaptor = argumentCaptor<Float>()
        verify(mockTransaction)
            .setPosition(
                eq(mockSurfaceControl),
                leftAfterMoveCaptor.capture(),
                topAfterMoveCaptor.capture(),
            )
        val rectAfterMove = Rect(STARTING_BOUNDS)
        rectAfterMove.offsetTo(
            leftAfterMoveCaptor.firstValue.toInt(),
            topAfterMoveCaptor.firstValue.toInt(),
        )
        Assert.assertFalse(DISPLAY_BOUNDS.intersect(rectAfterMove))
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_WINDOW_DROP_SMOOTH_TRANSITION)
    fun testDragResize_movesTaskToNewDisplayThenBackToOriginalDisplay() = runOnUiThread {
        taskPositioner.onDragPositioningStart(
            CTRL_TYPE_UNDEFINED,
            DISPLAY_ID_0,
            STARTING_BOUNDS.left.toFloat(),
            STARTING_BOUNDS.top.toFloat(),
        )

        // Move to the display 1
        taskPositioner.onDragPositioningMove(DISPLAY_ID_1, 200f, 800f)
        val rectAfterMove = Rect(200, -600, 300, -400)
        inOrder
            .verify(mockTransaction)
            .setPosition(any(), eq(rectAfterMove.left.toFloat()), eq(rectAfterMove.top.toFloat()))
        inOrder
            .verify(mockTransaction)
            .setAlpha(eq(mockWindowDecoration.taskSurface), eq(ALPHA_FOR_TRANSLUCENT_WINDOW))

        // Moving back to the original display
        taskPositioner.onDragPositioningMove(DISPLAY_ID_0, 100f, 1500f)
        rectAfterMove.set(100, 1500, 200, 1700)
        inOrder
            .verify(mockTransaction)
            .setPosition(any(), eq(rectAfterMove.left.toFloat()), eq(rectAfterMove.top.toFloat()))
        inOrder
            .verify(mockTransaction)
            .setAlpha(eq(mockWindowDecoration.taskSurface), eq(ALPHA_FOR_VISIBLE_WINDOW))

        // Finish the drag move on the original display
        val endBounds = taskPositioner.onDragPositioningEnd(DISPLAY_ID_0, 50f, 50f)
        rectAfterMove.set(50, 50, 150, 150)
        val rectAfterMove = Rect(50, 50, 150, 150)

        verify(mockWindowDecoration, never()).showResizeVeil(any())
        verify(mockWindowDecoration, never()).hideResizeVeil()
@@ -332,6 +372,43 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
        Assert.assertEquals(rectAfterMove, endBounds)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_WINDOW_DROP_SMOOTH_TRANSITION)
    fun testDragResize_movesTaskToNewDisplayThenBackToOriginalDisplay_movesTaskSurfaceOffscreen() =
        runOnUiThread {
            whenever(spyDisplayLayout0.width()).thenReturn(DISPLAY_BOUNDS.width())
            whenever(spyDisplayLayout0.height()).thenReturn(DISPLAY_BOUNDS.height())

            taskPositioner.onDragPositioningStart(
                CTRL_TYPE_UNDEFINED,
                DISPLAY_ID_0,
                STARTING_BOUNDS.left.toFloat(),
                STARTING_BOUNDS.top.toFloat(),
            )

            // Move to the display 1
            taskPositioner.onDragPositioningMove(DISPLAY_ID_1, 200f, 800f)

            // Moving back to the original display
            taskPositioner.onDragPositioningMove(DISPLAY_ID_0, 100f, 1500f)

            // Check that setPosition is only called once and the surface is outside of display.
            val leftAfterMoveCaptor = argumentCaptor<Float>()
            val topAfterMoveCaptor = argumentCaptor<Float>()
            verify(mockTransaction)
                .setPosition(
                    eq(mockSurfaceControl),
                    leftAfterMoveCaptor.capture(),
                    topAfterMoveCaptor.capture(),
                )
            val rectAfterMove = Rect(STARTING_BOUNDS)
            rectAfterMove.offsetTo(
                leftAfterMoveCaptor.firstValue.toInt(),
                topAfterMoveCaptor.firstValue.toInt(),
            )
            Assert.assertFalse(DISPLAY_BOUNDS.intersect(rectAfterMove))
        }

    @Test
    fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread {
        taskPositioner.onDragPositioningStart(
@@ -720,8 +797,6 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
        private const val NAVBAR_HEIGHT = 50
        private const val CAPTION_HEIGHT = 50
        private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
        private const val ALPHA_FOR_TRANSLUCENT_WINDOW = 0.7f
        private const val ALPHA_FOR_VISIBLE_WINDOW = 1.0f
        private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
        private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
        private val STABLE_BOUNDS_LANDSCAPE =