diff options
| author | 2025-01-15 16:51:54 -0800 | |
|---|---|---|
| committer | 2025-01-15 16:51:54 -0800 | |
| commit | 9d02f4165ba7102d2b369ed12335fef8c342eaef (patch) | |
| tree | a35ce47c7305e5c2db3d2648da852aa1afe3b1c5 | |
| parent | b1a14cd2048320ab18bf36bf89967f16f2f8f803 (diff) | |
| parent | c43787964956fa01b3d390773ff0f6b1346c9059 (diff) | |
Merge "Implement multi-display window drag" into main
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 |