summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Qijing Yao <yaoqq@google.com> 2025-01-15 16:51:54 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-01-15 16:51:54 -0800
commit9d02f4165ba7102d2b369ed12335fef8c342eaef (patch)
treea35ce47c7305e5c2db3d2648da852aa1afe3b1c5
parentb1a14cd2048320ab18bf36bf89967f16f2f8f803 (diff)
parentc43787964956fa01b3d390773ff0f6b1346c9059 (diff)
Merge "Implement multi-display window drag" into main
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt86
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt91
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt97
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt45
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt146
7 files changed, 461 insertions, 65 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt
new file mode 100644
index 000000000000..a13ad20f8c05
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculator.kt
@@ -0,0 +1,86 @@
+/*
+ * 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(),
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index f38957e48dbf..3d57038b27fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -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()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
index 8dc921c986ce..07496eb0e526 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -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,16 +163,47 @@ class MultiDisplayVeiledResizeTaskPositioner(
interactionJankMonitor.begin(
createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
)
+
val t = transactionSupplier.get()
- DragPositioningCallbackUtility.setPositionOnDrag(
- desktopWindowDecoration,
- repositionTaskBounds,
- taskBoundsAtDragStart,
- repositionStartPoint,
- t,
- x,
- y,
- )
+ 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,
+ taskBoundsAtDragStart,
+ repositionStartPoint,
+ t,
+ 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,13 +234,38 @@ class MultiDisplayVeiledResizeTaskPositioner(
}
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
} else {
- DragPositioningCallbackUtility.updateTaskBounds(
- repositionTaskBounds,
- taskBoundsAtDragStart,
- repositionStartPoint,
- x,
- y,
- )
+ 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,
+ repositionStartPoint,
+ 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)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt
new file mode 100644
index 000000000000..bd924c2b47c1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveBoundsCalculatorTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt
new file mode 100644
index 000000000000..c8bebf11a82c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt
@@ -0,0 +1,45 @@
+/*
+ * 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
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 2b986d184c20..4b749d1274b1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -257,6 +257,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
// Mock running tasks are registered here so we can get the list from mock shell task organizer
private val runningTasks = mutableListOf<RunningTaskInfo>()
+ private val SECONDARY_DISPLAY_ID = 1
private val DISPLAY_DIMENSION_SHORT = 1600
private val DISPLAY_DIMENSION_LONG = 2560
private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 75, 2240, 1275)
@@ -316,6 +317,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECONDARY_DISPLAY_ID))
+ .thenReturn(tda)
whenever(
mMockDesktopImmersiveController.exitImmersiveIfApplicable(
any(),
@@ -3588,6 +3591,45 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ fun onDesktopDragEnd_noIndicatorAndMoveToNewDisplay_reparent() {
+ val task = setUpFreeformTask()
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ 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,
+ position = Point(100, 200),
+ inputCoordinate = PointF(200f, 300f),
+ currentDragBounds,
+ validDragArea = Rect(0, 50, 2000, 2000),
+ dragStartBounds = Rect(),
+ motionEvent,
+ desktopWindowDecoration,
+ )
+
+ verify(transitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ Mockito.argThat { wct ->
+ return@argThat wct.hierarchyOps[0].isReparent
+ },
+ eq(null),
+ )
+ }
+
+ @Test
fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() {
val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))
val spyController = spy(controller)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
index f179cac32244..2207c705d7dc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -17,14 +17,14 @@ package com.android.wm.shell.windowdecor
import android.app.ActivityManager
import android.app.WindowConfiguration
-import android.content.Context
-import android.content.res.Resources
+import android.content.res.Configuration
import android.graphics.Point
import android.graphics.Rect
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.testing.AndroidTestingRunner
+import android.testing.TestableResources
import android.view.Display
import android.view.Surface.ROTATION_0
import android.view.Surface.ROTATION_270
@@ -41,6 +41,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.MultiDisplayTestUtil
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
@@ -55,6 +56,7 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
+import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -82,7 +84,6 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
@Mock private lateinit var taskBinder: IBinder
@Mock private lateinit var mockDisplayController: DisplayController
- @Mock private lateinit var mockDisplayLayout: DisplayLayout
@Mock private lateinit var mockDisplay: Display
@Mock private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction>
@Mock private lateinit var mockTransaction: SurfaceControl.Transaction
@@ -90,9 +91,11 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
@Mock private lateinit var mockTransitionInfo: TransitionInfo
@Mock private lateinit var mockFinishCallback: TransitionFinishCallback
@Mock private lateinit var mockTransitions: Transitions
- @Mock private lateinit var mockContext: Context
- @Mock private lateinit var mockResources: Resources
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ private lateinit var resources: TestableResources
+ private lateinit var spyDisplayLayout0: DisplayLayout
+ private lateinit var spyDisplayLayout1: DisplayLayout
+
private val mainHandler = Handler(Looper.getMainLooper())
private lateinit var taskPositioner: MultiDisplayVeiledResizeTaskPositioner
@@ -101,24 +104,45 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- mockDesktopWindowDecoration.mDisplay = mockDisplay
- mockDesktopWindowDecoration.mDecorWindowContext = mockContext
- whenever(mockContext.getResources()).thenReturn(mockResources)
whenever(taskToken.asBinder()).thenReturn(taskBinder)
- whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
- whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
- whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- if (
- mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
- .displayRotation == ROTATION_90 ||
+ mockDesktopWindowDecoration.mDisplay = mockDisplay
+ mockDesktopWindowDecoration.mDecorWindowContext = mContext
+ resources = mContext.orCreateTestableResources
+ val resourceConfiguration = Configuration()
+ resourceConfiguration.uiMode = 0
+ resources.overrideConfiguration(resourceConfiguration)
+ spyDisplayLayout0 =
+ MultiDisplayTestUtil.createSpyDisplayLayout(
+ MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_0,
+ MultiDisplayTestUtil.DISPLAY_DPI_0,
+ resources.resources,
+ )
+ spyDisplayLayout1 =
+ MultiDisplayTestUtil.createSpyDisplayLayout(
+ MultiDisplayTestUtil.DISPLAY_GLOBAL_BOUNDS_1,
+ MultiDisplayTestUtil.DISPLAY_DPI_1,
+ resources.resources,
+ )
+ whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID_0)).thenReturn(spyDisplayLayout0)
+ whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID_1)).thenReturn(spyDisplayLayout1)
+ whenever(spyDisplayLayout0.densityDpi()).thenReturn(DENSITY_DPI)
+ whenever(spyDisplayLayout1.densityDpi()).thenReturn(DENSITY_DPI)
+ doAnswer { i ->
+ val rect = i.getArgument<Rect>(0)
+ if (
mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
- .displayRotation == ROTATION_270
- ) {
- (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
- } else {
- (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ .displayRotation == ROTATION_90 ||
+ mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ rect.set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ rect.set(STABLE_BOUNDS_PORTRAIT)
+ }
+ null
}
- }
+ .`when`(spyDisplayLayout0)
+ .getStableBounds(any())
`when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
mockDesktopWindowDecoration.mTaskInfo =
ActivityManager.RunningTaskInfo().apply {
@@ -127,14 +151,14 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
minWidth = MIN_WIDTH
minHeight = MIN_HEIGHT
defaultMinSize = DEFAULT_MIN
- displayId = DISPLAY_ID
+ displayId = DISPLAY_ID_0
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
configuration.windowConfiguration.displayRotation = ROTATION_90
isResizeable = true
}
`when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockDesktopWindowDecoration.mDisplay = mockDisplay
- whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+ whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID_0 }
taskPositioner =
MultiDisplayVeiledResizeTaskPositioner(
@@ -153,14 +177,14 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningEnd(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
@@ -185,13 +209,13 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED,
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
taskPositioner.onDragPositioningMove(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat() + 60,
STARTING_BOUNDS.top.toFloat() + 100,
)
@@ -205,7 +229,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
val endBounds =
taskPositioner.onDragPositioningEnd(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat() + 70,
STARTING_BOUNDS.top.toFloat() + 20,
)
@@ -221,16 +245,39 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
+ fun testDragResize_movesTaskToNewDisplay() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED,
+ DISPLAY_ID_0,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ 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()))
+
+ val endBounds = taskPositioner.onDragPositioningEnd(DISPLAY_ID_1, 300f, 450f)
+ val rectAfterEnd = Rect(300, 450, 500, 650)
+
+ verify(mockDesktopWindowDecoration, never()).showResizeVeil(any())
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+ Assert.assertEquals(rectAfterEnd, endBounds)
+ }
+
+ @Test
fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
taskPositioner.onDragPositioningMove(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.right.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10,
)
@@ -252,7 +299,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
)
taskPositioner.onDragPositioningEnd(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.right.toFloat() + 20,
STARTING_BOUNDS.top.toFloat() + 20,
)
@@ -278,20 +325,20 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
@Test
fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread {
taskPositioner.onDragPositioningStart(
- DISPLAY_ID,
+ DISPLAY_ID_0,
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
taskPositioner.onDragPositioningMove(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
taskPositioner.onDragPositioningEnd(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10,
)
@@ -326,16 +373,16 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
val newX = STARTING_BOUNDS.left.toFloat() + 5
val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
- taskPositioner.onDragPositioningMove(DISPLAY_ID, newX, newY)
+ taskPositioner.onDragPositioningMove(DISPLAY_ID_0, newX, newY)
- taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID_0, newX, newY)
verify(mockShellTaskOrganizer, never())
.applyTransaction(
@@ -354,7 +401,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
mockDesktopWindowDecoration.mHasGlobalFocus = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
@@ -375,7 +422,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
mockDesktopWindowDecoration.mHasGlobalFocus = true
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
@@ -396,7 +443,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
mockDesktopWindowDecoration.mHasGlobalFocus = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
@@ -427,7 +474,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
rectAfterDrag.right += 2000
rectAfterDrag.bottom = STABLE_BOUNDS_LANDSCAPE.bottom
// First drag; we should fetch stable bounds.
- verify(mockDisplayLayout, times(1)).getStableBounds(any())
+ verify(spyDisplayLayout0, times(1)).getStableBounds(any())
verify(mockTransitions)
.startTransition(
eq(TRANSIT_CHANGE),
@@ -451,7 +498,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
)
// Display did not rotate; we should use previous stable bounds
- verify(mockDisplayLayout, times(1)).getStableBounds(any())
+ verify(spyDisplayLayout0, times(1)).getStableBounds(any())
// Rotate the screen to portrait
mockDesktopWindowDecoration.mTaskInfo.apply {
@@ -482,7 +529,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
eq(taskPositioner),
)
// Display has rotated; we expect a new stable bounds.
- verify(mockDisplayLayout, times(2)).getStableBounds(any())
+ verify(spyDisplayLayout0, times(2)).getStableBounds(any())
}
@Test
@@ -491,13 +538,13 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
taskPositioner.onDragPositioningMove(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat() - 20,
STARTING_BOUNDS.top.toFloat() - 20,
)
@@ -507,7 +554,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
verify(mockDragEventListener, times(1)).onDragMove(eq(TASK_ID))
taskPositioner.onDragPositioningEnd(
- DISPLAY_ID,
+ DISPLAY_ID_0,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat(),
)
@@ -568,10 +615,10 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
}
private fun performDrag(startX: Float, startY: Float, endX: Float, endY: Float, ctrlType: Int) {
- taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID, startX, startY)
- taskPositioner.onDragPositioningMove(DISPLAY_ID, endX, endY)
+ taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID_0, startX, startY)
+ taskPositioner.onDragPositioningMove(DISPLAY_ID_0, endX, endY)
- taskPositioner.onDragPositioningEnd(DISPLAY_ID, endX, endY)
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID_0, endX, endY)
}
companion object {
@@ -580,7 +627,8 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
private const val MIN_HEIGHT = 10
private const val DENSITY_DPI = 20
private const val DEFAULT_MIN = 40
- private const val DISPLAY_ID = 1
+ private const val DISPLAY_ID_0 = 0
+ private const val DISPLAY_ID_1 = 1
private const val NAVBAR_HEIGHT = 50
private const val CAPTION_HEIGHT = 50
private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10