diff options
5 files changed, 188 insertions, 19 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 59acdc574434..48fadc02ff1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -100,6 +100,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksLimiter; import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver; import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler; +import com.android.wm.shell.desktopmode.DragToDisplayTransitionHandler; import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.OverviewToDesktopTransitionObserver; @@ -770,7 +771,8 @@ public abstract class WMShellModule { DesksOrganizer desksOrganizer, DesksTransitionObserver desksTransitionObserver, UserProfileContexts userProfileContexts, - DesktopModeCompatPolicy desktopModeCompatPolicy) { + DesktopModeCompatPolicy desktopModeCompatPolicy, + DragToDisplayTransitionHandler dragToDisplayTransitionHandler) { return new DesktopTasksController( context, shellInit, @@ -808,7 +810,8 @@ public abstract class WMShellModule { desksOrganizer, desksTransitionObserver, userProfileContexts, - desktopModeCompatPolicy); + desktopModeCompatPolicy, + dragToDisplayTransitionHandler); } @WMSingleton @@ -934,6 +937,12 @@ public abstract class WMShellModule { @WMSingleton @Provides + static DragToDisplayTransitionHandler provideDragToDisplayTransitionHandler() { + return new DragToDisplayTransitionHandler(); + } + + @WMSingleton + @Provides static Optional<DesktopModeKeyGestureHandler> provideDesktopModeKeyGestureHandler( Context context, Optional<DesktopModeWindowDecorViewModel> desktopModeWindowDecorViewModel, 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 e8d6fcf49eaa..fca5084b65bc 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 @@ -205,6 +205,7 @@ class DesktopTasksController( private val desksTransitionObserver: DesksTransitionObserver, private val userProfileContexts: UserProfileContexts, private val desktopModeCompatPolicy: DesktopModeCompatPolicy, + private val dragToDisplayTransitionHandler: DragToDisplayTransitionHandler, ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, @@ -3188,25 +3189,24 @@ class DesktopTasksController( 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) - } + val newDisplayId = motionEvent.getDisplayId() + val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(newDisplayId) + val isCrossDisplayDrag = + Flags.enableConnectedDisplaysWindowDrag() && + newDisplayId != taskInfo.getDisplayId() && + displayAreaInfo != null + val handler = + if (isCrossDisplayDrag) { + dragToDisplayTransitionHandler + } else { + null } + if (isCrossDisplayDrag) { + // TODO: b/362720497 - reparent to a specific desk within the target display. + wct.reparent(taskInfo.token, displayAreaInfo.token, /* onTop= */ true) } - transitions.startTransition(TRANSIT_CHANGE, wct, null) + transitions.startTransition(TRANSIT_CHANGE, wct, handler) releaseVisualIndicator() } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandler.kt new file mode 100644 index 000000000000..d51576a5148e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandler.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2025 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.desktopmode + +import android.os.IBinder +import android.view.SurfaceControl +import android.window.TransitionInfo +import android.window.TransitionRequestInfo +import android.window.WindowContainerTransaction +import com.android.wm.shell.transition.Transitions + +/** Handles the transition to drag a window to another display by dragging the caption. */ +class DragToDisplayTransitionHandler : Transitions.TransitionHandler { + override fun handleRequest( + transition: IBinder, + request: TransitionRequestInfo, + ): WindowContainerTransaction? { + return null + } + + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: Transitions.TransitionFinishCallback, + ): Boolean { + for (change in info.changes) { + val sc = change.leash + val endBounds = change.endAbsBounds + val endPosition = change.endRelOffset + startTransaction + .setWindowCrop(sc, endBounds.width(), endBounds.height()) + .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat()) + finishTransaction + .setWindowCrop(sc, endBounds.width(), endBounds.height()) + .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat()) + } + + startTransaction.apply() + finishCallback.onTransitionFinished(null) + return true + } +} 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 253740c8c3fd..04acaef344eb 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 @@ -261,6 +261,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var mockDisplayContext: Context + @Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler private lateinit var controller: DesktopTasksController private lateinit var shellInit: ShellInit @@ -431,6 +432,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() desksTransitionsObserver, userProfileContexts, desktopModeCompatPolicy, + dragToDisplayTransitionHandler, ) @After @@ -5008,7 +5010,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Mockito.argThat { wct -> return@argThat wct.hierarchyOps[0].isReparent }, - eq(null), + eq(dragToDisplayTransitionHandler), ) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandlerTest.kt new file mode 100644 index 000000000000..51c302983fd0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDisplayTransitionHandlerTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2025 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.desktopmode + +import android.graphics.Point +import android.graphics.Rect +import android.os.IBinder +import android.view.SurfaceControl +import android.window.TransitionInfo +import android.window.TransitionRequestInfo +import com.android.wm.shell.transition.Transitions +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +/** + * Test class for {@link DragToDisplayTransitionHandler} + * + * Usage: atest WMShellUnitTests:DragToDisplayTransitionHandlerTest + */ +class DragToDisplayTransitionHandlerTest { + private lateinit var handler: DragToDisplayTransitionHandler + private val mockTransition: IBinder = mock() + private val mockRequestInfo: TransitionRequestInfo = mock() + private val mockTransitionInfo: TransitionInfo = mock() + private val mockStartTransaction: SurfaceControl.Transaction = mock() + private val mockFinishTransaction: SurfaceControl.Transaction = mock() + private val mockFinishCallback: Transitions.TransitionFinishCallback = mock() + + @Before + fun setUp() { + handler = DragToDisplayTransitionHandler() + whenever(mockStartTransaction.setWindowCrop(any(), any(), any())) + .thenReturn(mockStartTransaction) + whenever(mockFinishTransaction.setWindowCrop(any(), any(), any())) + .thenReturn(mockFinishTransaction) + } + + @Test + fun handleRequest_anyRequest_returnsNull() { + val result = handler.handleRequest(mockTransition, mockRequestInfo) + assert(result == null) + } + + @Test + fun startAnimation_verifyTransformationsApplied() { + val mockChange1 = mock<TransitionInfo.Change>() + val leash1 = mock<SurfaceControl>() + val endBounds1 = Rect(0, 0, 50, 50) + val endPosition1 = Point(5, 5) + + whenever(mockChange1.leash).doReturn(leash1) + whenever(mockChange1.endAbsBounds).doReturn(endBounds1) + whenever(mockChange1.endRelOffset).doReturn(endPosition1) + + val mockChange2 = mock<TransitionInfo.Change>() + val leash2 = mock<SurfaceControl>() + val endBounds2 = Rect(100, 100, 200, 150) + val endPosition2 = Point(15, 25) + + whenever(mockChange2.leash).doReturn(leash2) + whenever(mockChange2.endAbsBounds).doReturn(endBounds2) + whenever(mockChange2.endRelOffset).doReturn(endPosition2) + + whenever(mockTransitionInfo.changes).doReturn(listOf(mockChange1, mockChange2)) + + handler.startAnimation( + mockTransition, + mockTransitionInfo, + mockStartTransaction, + mockFinishTransaction, + mockFinishCallback, + ) + + verify(mockStartTransaction).setWindowCrop(leash1, endBounds1.width(), endBounds1.height()) + verify(mockStartTransaction) + .setPosition(leash1, endPosition1.x.toFloat(), endPosition1.y.toFloat()) + verify(mockStartTransaction).setWindowCrop(leash2, endBounds2.width(), endBounds2.height()) + verify(mockStartTransaction) + .setPosition(leash2, endPosition2.x.toFloat(), endPosition2.y.toFloat()) + verify(mockStartTransaction).apply() + verify(mockFinishCallback).onTransitionFinished(null) + } +} |