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

Commit de3139e3 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Specify transition handler for window drag across displays" into main

parents b56b1588 a840e4a3
Loading
Loading
Loading
Loading
+11 −2
Original line number Diff line number Diff line
@@ -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
@@ -932,6 +935,12 @@ public abstract class WMShellModule {
                        interactionJankMonitor, bubbleController);
    }

    @WMSingleton
    @Provides
    static DragToDisplayTransitionHandler provideDragToDisplayTransitionHandler() {
        return new DragToDisplayTransitionHandler();
    }

    @WMSingleton
    @Provides
    static Optional<DesktopModeKeyGestureHandler> provideDesktopModeKeyGestureHandler(
+16 −16
Original line number Diff line number Diff line
@@ -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,
                            )
                val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(newDisplayId)
                val isCrossDisplayDrag =
                    Flags.enableConnectedDisplaysWindowDrag() &&
                        newDisplayId != taskInfo.getDisplayId() &&
                        displayAreaInfo != null
                val handler =
                    if (isCrossDisplayDrag) {
                        dragToDisplayTransitionHandler
                    } else {
                            wct.reparent(taskInfo.token, displayAreaInfo.token, /* onTop= */ true)
                        }
                        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()
            }
+57 −0
Original line number Diff line number Diff line
/*
 * 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
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -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),
            )
    }

+101 −0
Original line number Diff line number Diff line
/*
 * 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)
    }
}