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

Commit 9d02f416 authored by Qijing Yao's avatar Qijing Yao Committed by Android (Google) Code Review
Browse files

Merge "Implement multi-display window drag" into main

parents b1a14cd2 c4378796
Loading
Loading
Loading
Loading
+86 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.common

import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF

/**
 * Utility class for calculating bounds during multi-display drag operations.
 *
 * This class provides helper functions to perform bounds calculation during window drag.
 */
object MultiDisplayDragMoveBoundsCalculator {
    /**
     * Calculates the global DP bounds of a window being dragged across displays.
     *
     * @param startDisplayLayout The DisplayLayout object of the display where the drag started.
     * @param repositionStartPoint The starting position of the drag (in pixels), relative to the
     *   display where the drag started.
     * @param boundsAtDragStart The initial bounds of the window (in pixels), relative to the
     *   display where the drag started.
     * @param currentDisplayLayout The DisplayLayout object of the display where the pointer is
     *   currently located.
     * @param x The current x-coordinate of the drag pointer (in pixels).
     * @param y The current y-coordinate of the drag pointer (in pixels).
     * @return A RectF object representing the calculated global DP bounds of the window.
     */
    fun calculateGlobalDpBoundsForDrag(
        startDisplayLayout: DisplayLayout,
        repositionStartPoint: PointF,
        boundsAtDragStart: Rect,
        currentDisplayLayout: DisplayLayout,
        x: Float,
        y: Float,
    ): RectF {
        // Convert all pixel values to DP.
        val startCursorDp =
            startDisplayLayout.localPxToGlobalDp(repositionStartPoint.x, repositionStartPoint.y)
        val currentCursorDp = currentDisplayLayout.localPxToGlobalDp(x, y)
        val startLeftTopDp =
            startDisplayLayout.localPxToGlobalDp(boundsAtDragStart.left, boundsAtDragStart.top)
        val widthDp = startDisplayLayout.pxToDp(boundsAtDragStart.width())
        val heightDp = startDisplayLayout.pxToDp(boundsAtDragStart.height())

        // Calculate DP bounds based on pointer movement delta.
        val currentLeftDp = startLeftTopDp.x + (currentCursorDp.x - startCursorDp.x)
        val currentTopDp = startLeftTopDp.y + (currentCursorDp.y - startCursorDp.y)
        val currentRightDp = currentLeftDp + widthDp
        val currentBottomDp = currentTopDp + heightDp

        return RectF(currentLeftDp, currentTopDp, currentRightDp, currentBottomDp)
    }

    /**
     * Converts global DP bounds to local pixel bounds for a specific display.
     *
     * @param rectDp The global DP bounds to convert.
     * @param displayLayout The DisplayLayout representing the display to convert the bounds to.
     * @return A Rect object representing the local pixel bounds on the specified display.
     */
    fun convertGlobalDpToLocalPxForRect(rectDp: RectF, displayLayout: DisplayLayout): Rect {
        val leftTopPxDisplay = displayLayout.globalDpToLocalPx(rectDp.left, rectDp.top)
        val rightBottomPxDisplay = displayLayout.globalDpToLocalPx(rectDp.right, rectDp.bottom)
        return Rect(
            leftTopPxDisplay.x.toInt(),
            leftTopPxDisplay.y.toInt(),
            rightBottomPxDisplay.x.toInt(),
            rightBottomPxDisplay.y.toInt(),
        )
    }
}
+19 −0
Original line number Diff line number Diff line
@@ -2425,6 +2425,25 @@ class DesktopTasksController(
                // Update task bounds so that the task position will match the position of its leash
                val wct = WindowContainerTransaction()
                wct.setBounds(taskInfo.token, destinationBounds)

                // TODO: b/362720497 - reparent to a specific desk within the target display.
                // Reparent task if it has been moved to a new display.
                if (Flags.enableConnectedDisplaysWindowDrag()) {
                    val newDisplayId = motionEvent.getDisplayId()
                    if (newDisplayId != taskInfo.getDisplayId()) {
                        val displayAreaInfo =
                            rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(newDisplayId)
                        if (displayAreaInfo == null) {
                            logW(
                                "Task reparent cannot find DisplayAreaInfo for displayId=%d",
                                newDisplayId,
                            )
                        } else {
                            wct.reparent(taskInfo.token, displayAreaInfo.token, /* onTop= */ true)
                        }
                    }
                }

                transitions.startTransition(TRANSIT_CHANGE, wct, null)

                releaseVisualIndicator()
+75 −16
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.transition.Transitions
import java.util.concurrent.TimeUnit
@@ -69,6 +70,7 @@ class MultiDisplayVeiledResizeTaskPositioner(
    @DragPositioningCallback.CtrlType private var ctrlType = 0
    private var isResizingOrAnimatingResize = false
    @Surface.Rotation private var rotation = 0
    private var startDisplayId = 0

    constructor(
        taskOrganizer: ShellTaskOrganizer,
@@ -95,6 +97,7 @@ class MultiDisplayVeiledResizeTaskPositioner(

    override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect {
        this.ctrlType = ctrlType
        startDisplayId = displayId
        taskBoundsAtDragStart.set(
            desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.bounds
        )
@@ -160,7 +163,13 @@ class MultiDisplayVeiledResizeTaskPositioner(
            interactionJankMonitor.begin(
                createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
            )

            val t = transactionSupplier.get()
            val startDisplayLayout = displayController.getDisplayLayout(startDisplayId)
            val currentDisplayLayout = displayController.getDisplayLayout(displayId)

            if (startDisplayLayout == null || currentDisplayLayout == null) {
                // Fall back to single-display drag behavior if any display layout is unavailable.
                DragPositioningCallbackUtility.setPositionOnDrag(
                    desktopWindowDecoration,
                    repositionTaskBounds,
@@ -170,6 +179,31 @@ class MultiDisplayVeiledResizeTaskPositioner(
                    x,
                    y,
                )
            } else {
                val boundsDp =
                    MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
                        startDisplayLayout,
                        repositionStartPoint,
                        taskBoundsAtDragStart,
                        currentDisplayLayout,
                        x,
                        y,
                    )
                repositionTaskBounds.set(
                    MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
                        boundsDp,
                        startDisplayLayout,
                    )
                )

                // TODO(b/383069173): Render drag indicator(s)

                t.setPosition(
                    desktopWindowDecoration.mTaskSurface,
                    repositionTaskBounds.left.toFloat(),
                    repositionTaskBounds.top.toFloat(),
                )
            }
            t.setFrameTimeline(Choreographer.getInstance().vsyncId)
            t.apply()
        }
@@ -200,6 +234,11 @@ class MultiDisplayVeiledResizeTaskPositioner(
            }
            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
        } else {
            val startDisplayLayout = displayController.getDisplayLayout(startDisplayId)
            val currentDisplayLayout = displayController.getDisplayLayout(displayId)

            if (startDisplayLayout == null || currentDisplayLayout == null) {
                // Fall back to single-display drag behavior if any display layout is unavailable.
                DragPositioningCallbackUtility.updateTaskBounds(
                    repositionTaskBounds,
                    taskBoundsAtDragStart,
@@ -207,6 +246,26 @@ class MultiDisplayVeiledResizeTaskPositioner(
                    x,
                    y,
                )
            } else {
                val boundsDp =
                    MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
                        startDisplayLayout,
                        repositionStartPoint,
                        taskBoundsAtDragStart,
                        currentDisplayLayout,
                        x,
                        y,
                    )
                repositionTaskBounds.set(
                    MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
                        boundsDp,
                        currentDisplayLayout,
                    )
                )

                // TODO(b/383069173): Clear drag indicator(s)
            }

            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
        }

+97 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.common

import android.content.res.Configuration
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.testing.TestableResources
import com.android.wm.shell.ShellTestCase
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test

/**
 * Tests for [MultiDisplayDragMoveBoundsCalculator].
 *
 * Build/Install/Run: atest WMShellUnitTests:MultiDisplayDragMoveBoundsCalculatorTest
 */
class MultiDisplayDragMoveBoundsCalculatorTest : ShellTestCase() {
    private lateinit var resources: TestableResources

    @Before
    fun setUp() {
        resources = mContext.getOrCreateTestableResources()
        val configuration = Configuration()
        configuration.uiMode = 0
        resources.overrideConfiguration(configuration)
    }

    @Test
    fun testCalculateGlobalDpBoundsForDrag() {
        val repositionStartPoint = PointF(20f, 40f)
        val boundsAtDragStart = Rect(10, 20, 110, 120)
        val x = 300f
        val y = 400f
        val displayLayout0 =
            MultiDisplayTestUtil.createSpyDisplayLayout(
                MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_0,
                MultiDisplayTestUtil.DISPLAY_DPI_0,
                resources.resources,
            )
        val displayLayout1 =
            MultiDisplayTestUtil.createSpyDisplayLayout(
                MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1,
                MultiDisplayTestUtil.DISPLAY_DPI_1,
                resources.resources,
            )

        val actualBoundsDp =
            MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
                displayLayout0,
                repositionStartPoint,
                boundsAtDragStart,
                displayLayout1,
                x,
                y,
            )

        val expectedBoundsDp = RectF(240f, -820f, 340f, -720f)
        assertEquals(expectedBoundsDp, actualBoundsDp)
    }

    @Test
    fun testConvertGlobalDpToLocalPxForRect() {
        val displayLayout =
            MultiDisplayTestUtil.createSpyDisplayLayout(
                MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1,
                MultiDisplayTestUtil.DISPLAY_DPI_1,
                resources.resources,
            )
        val rectDp = RectF(150f, -350f, 300f, -250f)

        val actualBoundsPx =
            MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
                rectDp,
                displayLayout,
            )

        val expectedBoundsPx = Rect(100, 1300, 400, 1500)
        assertEquals(expectedBoundsPx, actualBoundsPx)
    }
}
+45 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.common

import android.content.res.Resources
import android.graphics.RectF
import android.util.DisplayMetrics
import android.view.DisplayInfo
import org.mockito.Mockito.spy

/** Utility class for tests of [DesktopModeWindowDecorViewModel] */
object MultiDisplayTestUtil {
    // We have two displays, display#1 is placed on middle top of display#0:
    //   +---+
    //   | 1 |
    // +-+---+-+
    // |   0   |
    // +-------+
    val DISPLAY_GLOBAL_BOUNDS_0 = RectF(0f, 0f, 1200f, 800f)
    val DISPLAY_GLOBAL_BOUNDS_1 = RectF(100f, -1000f, 1100f, 0f)
    val DISPLAY_DPI_0 = DisplayMetrics.DENSITY_DEFAULT
    val DISPLAY_DPI_1 = DisplayMetrics.DENSITY_DEFAULT * 2

    fun createSpyDisplayLayout(globalBounds: RectF, dpi: Int, resources: Resources): DisplayLayout {
        val displayInfo = DisplayInfo()
        displayInfo.logicalDensityDpi = dpi
        val displayLayout = spy(DisplayLayout(displayInfo, resources, true, true))
        displayLayout.setGlobalBoundsDp(globalBounds)
        return displayLayout
    }
}
Loading