Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt +12 −0 Original line number Diff line number Diff line Loading @@ -136,4 +136,16 @@ class MultiDisplayDragMoveIndicatorController( } } } /** * Disposes all of the indicator surfaces with the [transaction]. */ fun disposeAllIndicators(transaction: SurfaceControl.Transaction) { dragIndicators.values.forEach { innerIndicatorMap -> innerIndicatorMap.values.forEach { indicator -> indicator.dispose(transaction) } } dragIndicators.clear() } } libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDragTransitionHandler.kt +19 −0 Original line number Diff line number Diff line Loading @@ -66,4 +66,23 @@ class WindowDragTransitionHandler( finishCallback.onTransitionFinished(null) return true } override fun onTransitionConsumed( transition: IBinder, aborted: Boolean, finishTransaction: SurfaceControl.Transaction?, ) { // Cleans up drag indicators when a transition is consumed or aborted. // TODO: b/444066236 - Track pending transitions to clear indicators for the correct task. // The taskId is not available in this callback, so we must dispose of all indicators // across all displays. This has a known side effect: in the rare edge case that a user is // dragging multiple windows simultaneously and one drag transition aborts, the indicators // for all dragged windows will be removed. This is an acceptable trade-off due to the low // probability of this scenario. if (DesktopExperienceFlags.ENABLE_WINDOW_DROP_SMOOTH_TRANSITION.isTrue) { finishTransaction?.let { multiDisplayDragMoveIndicatorController.disposeAllIndicators(finishTransaction) } } } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt +51 −0 Original line number Diff line number Diff line Loading @@ -56,10 +56,13 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { private val shellDesktopState = FakeShellDesktopState(FakeDesktopState()) private val indicatorSurface0 = mock<MultiDisplayDragMoveIndicatorSurface>() private val indicatorSurface1 = mock<MultiDisplayDragMoveIndicatorSurface>() private val indicatorSurface2 = mock<MultiDisplayDragMoveIndicatorSurface>() private val transaction = mock<SurfaceControl.Transaction>() private val transactionSupplier = mock<Supplier<SurfaceControl.Transaction>>() private val taskInfo = mock<RunningTaskInfo>() private val taskInfo2 = mock<RunningTaskInfo>() private val taskLeash = mock<SurfaceControl>() private val taskLeash2 = mock<SurfaceControl>() private val displayContext0 = mock<Context>() private val displayContext1 = mock<Context>() private lateinit var spyDisplayLayout0: DisplayLayout Loading Loading @@ -89,6 +92,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { spyDisplayLayout1 = TestDisplay.DISPLAY_1.getSpyDisplayLayout(resources.resources) taskInfo.taskId = TASK_ID taskInfo2.taskId = TASK_ID_2 whenever(displayController.getDisplayLayout(0)).thenReturn(spyDisplayLayout0) whenever(displayController.getDisplayLayout(1)).thenReturn(spyDisplayLayout1) whenever(displayController.getDisplayContext(0)).thenReturn(displayContext0) Loading @@ -97,6 +101,8 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { .thenReturn(indicatorSurface0) whenever(indicatorSurfaceFactory.create(eq(displayContext1), eq(taskLeash))) .thenReturn(indicatorSurface1) whenever(indicatorSurfaceFactory.create(eq(displayContext0), eq(taskLeash2))) .thenReturn(indicatorSurface2) whenever(transactionSupplier.get()).thenReturn(transaction) shellDesktopState.canBeWindowDropTarget = true } Loading Loading @@ -239,7 +245,52 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { verify(indicatorSurface1).dispose(transaction) } @Test @EnableFlags( Flags.FLAG_ENABLE_WINDOW_DROP_SMOOTH_TRANSITION, Flags.FLAG_ENABLE_BLOCK_NON_DESKTOP_DISPLAY_WINDOW_DRAG_BUGFIX, ) fun disposeAllIndicators_verifyAllIndicatorsDisposed() { // Drag a first task to create indicators on two displays controller.onDragMove( RectF(100f, -100f, 200f, 200f), // intersect with display 0 and 1 currentDisplayId = 1, startDisplayId = 0, taskLeash, taskInfo, displayIds = setOf(0, 1), ) { transaction } // Drag the second task to create an indicator on one display controller.onDragMove( RectF(150f, 150f, 250f, 250f), // intersect with display 0 only currentDisplayId = 0, startDisplayId = 0, taskLeash2, taskInfo2, displayIds = setOf(0, 1), ) { transaction } // Verify indicators for both tasks are created verify(indicatorSurfaceFactory).create(eq(displayContext0), eq(taskLeash)) verify(indicatorSurfaceFactory).create(eq(displayContext1), eq(taskLeash)) verify(indicatorSurfaceFactory).create(eq(displayContext0), eq(taskLeash2)) // Dispose all indicators controller.disposeAllIndicators(transaction) // Verify indicators for both tasks are disposed verify(indicatorSurface0).dispose(transaction) verify(indicatorSurface1).dispose(transaction) verify(indicatorSurface2).dispose(transaction) } companion object { private const val TASK_ID = 10 private const val TASK_ID_2 = 11 } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDragTransitionHandlerTest.kt +8 −0 Original line number Diff line number Diff line Loading @@ -117,4 +117,12 @@ class WindowDragTransitionHandlerTest : ShellTestCase() { verify(mockMultiDisplayDragMoveIndicatorController) .onDragEnd(eq(10), eq(mockFinishTransaction)) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOW_DROP_SMOOTH_TRANSITION) fun onTransitionConsumed_disposesIndicators() { handler.onTransitionConsumed(mockTransition, false, mockFinishTransaction) verify(mockMultiDisplayDragMoveIndicatorController) .disposeAllIndicators(mockFinishTransaction) } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorController.kt +12 −0 Original line number Diff line number Diff line Loading @@ -136,4 +136,16 @@ class MultiDisplayDragMoveIndicatorController( } } } /** * Disposes all of the indicator surfaces with the [transaction]. */ fun disposeAllIndicators(transaction: SurfaceControl.Transaction) { dragIndicators.values.forEach { innerIndicatorMap -> innerIndicatorMap.values.forEach { indicator -> indicator.dispose(transaction) } } dragIndicators.clear() } }
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDragTransitionHandler.kt +19 −0 Original line number Diff line number Diff line Loading @@ -66,4 +66,23 @@ class WindowDragTransitionHandler( finishCallback.onTransitionFinished(null) return true } override fun onTransitionConsumed( transition: IBinder, aborted: Boolean, finishTransaction: SurfaceControl.Transaction?, ) { // Cleans up drag indicators when a transition is consumed or aborted. // TODO: b/444066236 - Track pending transitions to clear indicators for the correct task. // The taskId is not available in this callback, so we must dispose of all indicators // across all displays. This has a known side effect: in the rare edge case that a user is // dragging multiple windows simultaneously and one drag transition aborts, the indicators // for all dragged windows will be removed. This is an acceptable trade-off due to the low // probability of this scenario. if (DesktopExperienceFlags.ENABLE_WINDOW_DROP_SMOOTH_TRANSITION.isTrue) { finishTransaction?.let { multiDisplayDragMoveIndicatorController.disposeAllIndicators(finishTransaction) } } } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayDragMoveIndicatorControllerTest.kt +51 −0 Original line number Diff line number Diff line Loading @@ -56,10 +56,13 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { private val shellDesktopState = FakeShellDesktopState(FakeDesktopState()) private val indicatorSurface0 = mock<MultiDisplayDragMoveIndicatorSurface>() private val indicatorSurface1 = mock<MultiDisplayDragMoveIndicatorSurface>() private val indicatorSurface2 = mock<MultiDisplayDragMoveIndicatorSurface>() private val transaction = mock<SurfaceControl.Transaction>() private val transactionSupplier = mock<Supplier<SurfaceControl.Transaction>>() private val taskInfo = mock<RunningTaskInfo>() private val taskInfo2 = mock<RunningTaskInfo>() private val taskLeash = mock<SurfaceControl>() private val taskLeash2 = mock<SurfaceControl>() private val displayContext0 = mock<Context>() private val displayContext1 = mock<Context>() private lateinit var spyDisplayLayout0: DisplayLayout Loading Loading @@ -89,6 +92,7 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { spyDisplayLayout1 = TestDisplay.DISPLAY_1.getSpyDisplayLayout(resources.resources) taskInfo.taskId = TASK_ID taskInfo2.taskId = TASK_ID_2 whenever(displayController.getDisplayLayout(0)).thenReturn(spyDisplayLayout0) whenever(displayController.getDisplayLayout(1)).thenReturn(spyDisplayLayout1) whenever(displayController.getDisplayContext(0)).thenReturn(displayContext0) Loading @@ -97,6 +101,8 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { .thenReturn(indicatorSurface0) whenever(indicatorSurfaceFactory.create(eq(displayContext1), eq(taskLeash))) .thenReturn(indicatorSurface1) whenever(indicatorSurfaceFactory.create(eq(displayContext0), eq(taskLeash2))) .thenReturn(indicatorSurface2) whenever(transactionSupplier.get()).thenReturn(transaction) shellDesktopState.canBeWindowDropTarget = true } Loading Loading @@ -239,7 +245,52 @@ class MultiDisplayDragMoveIndicatorControllerTest : ShellTestCase() { verify(indicatorSurface1).dispose(transaction) } @Test @EnableFlags( Flags.FLAG_ENABLE_WINDOW_DROP_SMOOTH_TRANSITION, Flags.FLAG_ENABLE_BLOCK_NON_DESKTOP_DISPLAY_WINDOW_DRAG_BUGFIX, ) fun disposeAllIndicators_verifyAllIndicatorsDisposed() { // Drag a first task to create indicators on two displays controller.onDragMove( RectF(100f, -100f, 200f, 200f), // intersect with display 0 and 1 currentDisplayId = 1, startDisplayId = 0, taskLeash, taskInfo, displayIds = setOf(0, 1), ) { transaction } // Drag the second task to create an indicator on one display controller.onDragMove( RectF(150f, 150f, 250f, 250f), // intersect with display 0 only currentDisplayId = 0, startDisplayId = 0, taskLeash2, taskInfo2, displayIds = setOf(0, 1), ) { transaction } // Verify indicators for both tasks are created verify(indicatorSurfaceFactory).create(eq(displayContext0), eq(taskLeash)) verify(indicatorSurfaceFactory).create(eq(displayContext1), eq(taskLeash)) verify(indicatorSurfaceFactory).create(eq(displayContext0), eq(taskLeash2)) // Dispose all indicators controller.disposeAllIndicators(transaction) // Verify indicators for both tasks are disposed verify(indicatorSurface0).dispose(transaction) verify(indicatorSurface1).dispose(transaction) verify(indicatorSurface2).dispose(transaction) } companion object { private const val TASK_ID = 10 private const val TASK_ID_2 = 11 } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDragTransitionHandlerTest.kt +8 −0 Original line number Diff line number Diff line Loading @@ -117,4 +117,12 @@ class WindowDragTransitionHandlerTest : ShellTestCase() { verify(mockMultiDisplayDragMoveIndicatorController) .onDragEnd(eq(10), eq(mockFinishTransaction)) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOW_DROP_SMOOTH_TRANSITION) fun onTransitionConsumed_disposesIndicators() { handler.onTransitionConsumed(mockTransition, false, mockFinishTransaction) verify(mockMultiDisplayDragMoveIndicatorController) .disposeAllIndicators(mockFinishTransaction) } }