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

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

Merge "Handle desktop app launches in DesktopMixedTransitionHandler" into main

parents f9f762c5 82af9d86
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -970,7 +970,10 @@ public abstract class WMShellModule {
            CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
            Optional<DesktopImmersiveController> desktopImmersiveController,
            InteractionJankMonitor interactionJankMonitor,
            @ShellMainThread Handler handler) {
            @ShellMainThread Handler handler,
            ShellInit shellInit,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
    ) {
        if (!DesktopModeStatus.canEnterDesktopMode(context)) {
            return Optional.empty();
        }
@@ -983,7 +986,9 @@ public abstract class WMShellModule {
                        closeDesktopTaskTransitionHandler,
                        desktopImmersiveController.get(),
                        interactionJankMonitor,
                        handler));
                        handler,
                        shellInit,
                        rootTaskDisplayAreaOrganizer));
    }

    @WMSingleton
+58 −6
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.view.SurfaceControl
import android.view.WindowManager
import android.window.DesktopModeFlags
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.VisibleForTesting
@@ -32,10 +33,13 @@ import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_C
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.MixedTransitionHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
@@ -50,8 +54,14 @@ class DesktopMixedTransitionHandler(
    private val desktopImmersiveController: DesktopImmersiveController,
    private val interactionJankMonitor: InteractionJankMonitor,
    @ShellMainThread private val handler: Handler,
    shellInit: ShellInit,
    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
) : MixedTransitionHandler, FreeformTaskTransitionStarter {

    init {
        shellInit.addInitCallback ({ transitions.addHandler(this) }, this)
    }

    @VisibleForTesting
    val pendingMixedTransitions = mutableListOf<PendingMixedTransition>()

@@ -85,9 +95,11 @@ class DesktopMixedTransitionHandler(
        @WindowManager.TransitionType transitionType: Int,
        wct: WindowContainerTransaction,
        taskId: Int,
        minimizingTaskId: Int? = null,
        exitingImmersiveTask: Int? = null,
    ): IBinder {
        if (!Flags.enableFullyImmersiveInDesktop()) {
        if (!Flags.enableFullyImmersiveInDesktop() &&
            !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
            return transitions.startTransition(transitionType, wct, /* handler= */ null)
        }
        if (exitingImmersiveTask == null) {
@@ -103,11 +115,17 @@ class DesktopMixedTransitionHandler(
                pendingMixedTransitions.add(PendingMixedTransition.Launch(
                    transition = transition,
                    launchingTask = taskId,
                    exitingImmersiveTask = exitingImmersiveTask
                    minimizingTask = minimizingTaskId,
                    exitingImmersiveTask = exitingImmersiveTask,
                ))
            }
    }

    /** Notifies this handler that there is a pending transition for it to handle. */
    fun addPendingMixedTransition(pendingMixedTransition: PendingMixedTransition) {
        pendingMixedTransitions.add(pendingMixedTransition)
    }

    /** Returns null, as it only handles transitions started from Shell. */
    override fun handleRequest(
        transition: IBinder,
@@ -123,7 +141,7 @@ class DesktopMixedTransitionHandler(
    ): Boolean {
        val pending = pendingMixedTransitions.find { pending -> pending.transition == transition }
            ?: return false.also {
                logW("Should have pending desktop transition")
                logV("No pending desktop transition")
            }
        pendingMixedTransitions.remove(pending)
        logV("Animating pending mixed transition: %s", pending)
@@ -191,6 +209,9 @@ class DesktopMixedTransitionHandler(
        val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask ->
            findDesktopTaskChange(info, exitingTask)
        }
        val minimizeChange = pending.minimizingTask?.let { minimizingTask ->
            findDesktopTaskChange(info, minimizingTask)
        }
        val launchChange = findDesktopTaskChange(info, pending.launchingTask)
            ?: error("Should have pending launching task change")

@@ -204,9 +225,17 @@ class DesktopMixedTransitionHandler(
        }

        logV(
            "Animating pending mixed launch transition task#%d immersiveExitTask#%s",
            launchChange.taskInfo!!.taskId, immersiveExitChange?.taskInfo?.taskId
            "Animating mixed launch transition task#%d, minimizingTask#%s immersiveExitTask#%s",
            launchChange.taskInfo!!.taskId, minimizeChange?.taskInfo?.taskId,
            immersiveExitChange?.taskInfo?.taskId
        )
        if (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
            // Only apply minimize change reparenting here if we implement the new app launch
            // transitions, otherwise this reparenting is handled in the default handler.
            minimizeChange?.let {
                applyMinimizeChangeReparenting(info, minimizeChange, startTransaction)
            }
        }
        if (immersiveExitChange != null) {
            subAnimationCount = 2
            // Animate the immersive exit change separately.
@@ -257,7 +286,7 @@ class DesktopMixedTransitionHandler(
        change: TransitionInfo.Change,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction,
        finishCallback: Transitions.TransitionFinishCallback,
        finishCallback: TransitionFinishCallback,
    ): Boolean {
        // Starting the jank trace if closing the last window in desktop mode.
        interactionJankMonitor.begin(
@@ -280,6 +309,28 @@ class DesktopMixedTransitionHandler(
        )
    }

    /**
     * Reparent the minimizing task back to its root display area.
     *
     * During the launch/minimize animation the all animated tasks will be reparented to a
     * transition leash shown in front of other desktop tasks. Reparenting the minimizing task back
     * to its root display area ensures that task stays behind other desktop tasks during the
     * animation.
     */
    private fun applyMinimizeChangeReparenting(
        info: TransitionInfo,
        minimizeChange: Change,
        startTransaction: SurfaceControl.Transaction,
    ) {
        require(TransitionUtil.isOpeningMode(info.type))
        require(minimizeChange.taskInfo != null)
        val taskInfo = minimizeChange.taskInfo!!
        require(taskInfo.isFreeform)
        logV("Reparenting minimizing task#%d", taskInfo.taskId)
        rootTaskDisplayAreaOrganizer.reparentToDisplayArea(
            taskInfo.displayId, minimizeChange.leash, startTransaction)
    }

    private fun dispatchToLeftoverHandler(
        transition: IBinder,
        info: TransitionInfo,
@@ -341,6 +392,7 @@ class DesktopMixedTransitionHandler(
        data class Launch(
            override val transition: IBinder,
            val launchingTask: Int,
            val minimizingTask: Int?,
            val exitingImmersiveTask: Int?,
        ) : PendingMixedTransition()
    }
+18 −0
Original line number Diff line number Diff line
@@ -698,6 +698,7 @@ class DesktopTasksController(
                transitionType = transitionType,
                wct = wct,
                taskId = taskId,
                minimizingTaskId = taskIdToMinimize,
                exitingImmersiveTask = exitingImmersiveTask,
            )
            taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
@@ -1434,6 +1435,7 @@ class DesktopTasksController(
                )
            val transition = transitions.startTransition(TRANSIT_OPEN, wct, null)
            taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
            addPendingAppLaunchTransition(transition, requestedTaskId, taskIdToMinimize)
            exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
        } else {
            val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
@@ -1574,6 +1576,7 @@ class DesktopTasksController(
        desktopImmersiveController.exitImmersiveIfApplicable(transition, wct, task.displayId)
        // 2) minimize a Task if needed.
        val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
        addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
        if (taskIdToMinimize != null) {
            addPendingMinimizeTransition(transition, taskIdToMinimize)
            return wct
@@ -1605,6 +1608,7 @@ class DesktopTasksController(
                // minimize another Task.
                val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
                taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
                addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
                desktopImmersiveController.exitImmersiveIfApplicable(
                    transition, wct, task.displayId
                )
@@ -1785,6 +1789,20 @@ class DesktopTasksController(
        }
    }

    private fun addPendingAppLaunchTransition(
        transition: IBinder,
        launchTaskId: Int,
        minimizeTaskId: Int?,
    ) {
        if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
            return
        }
        // TODO b/359523924: pass immersive task here?
        desktopMixedTransitionHandler.addPendingMixedTransition(
            DesktopMixedTransitionHandler.PendingMixedTransition.Launch(
                transition, launchTaskId, minimizeTaskId, /* exitingImmersiveTask= */ null))
    }

    fun removeDesktop(displayId: Int) {
        if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return

+160 −3
Original line number Diff line number Diff line
@@ -39,9 +39,12 @@ import androidx.test.filters.SmallTest
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler.PendingMixedTransition
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertFalse
@@ -51,7 +54,9 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
@@ -80,6 +85,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
    @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
    @Mock lateinit var mockHandler: Handler
    @Mock lateinit var closingTaskLeash: SurfaceControl
    @Mock lateinit var shellInit: ShellInit
    @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer

    private lateinit var mixedHandler: DesktopMixedTransitionHandler

@@ -94,7 +101,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
                closeDesktopTaskTransitionHandler,
                desktopImmersiveController,
                interactionJankMonitor,
                mockHandler
                mockHandler,
                shellInit,
                rootTaskDisplayAreaOrganizer,
            )
    }

@@ -238,8 +247,10 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
    fun startLaunchTransition_immersiveMixDisabled_doesNotUseMixedHandler() {
    @DisableFlags(
        Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
    fun startLaunchTransition_immersiveAndAppLaunchFlagsDisabled_doesNotUseMixedHandler() {
        val wct = WindowContainerTransaction()
        val task = createTask(WINDOWING_MODE_FREEFORM)
        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
@@ -273,6 +284,24 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
        verify(transitions).startTransition(TRANSIT_OPEN, wct, mixedHandler)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
    fun startLaunchTransition_desktopAppLaunchEnabled_usesMixedHandler() {
        val wct = WindowContainerTransaction()
        val task = createTask(WINDOWING_MODE_FREEFORM)
        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
            .thenReturn(Binder())

        mixedHandler.startLaunchTransition(
            transitionType = TRANSIT_OPEN,
            wct = wct,
            taskId = task.taskId,
            exitingImmersiveTask = null
        )

        verify(transitions).startTransition(TRANSIT_OPEN, wct, mixedHandler)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
    fun startAndAnimateLaunchTransition_withoutImmersiveChange_dispatchesAllChangesToLeftOver() {
@@ -354,6 +383,134 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
        )
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
    fun startAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
        val wct = WindowContainerTransaction()
        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
        val launchTaskChange = createChange(launchingTask)
        val transition = Binder()
        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
            .thenReturn(transition)

        mixedHandler.startLaunchTransition(
            transitionType = TRANSIT_OPEN,
            wct = wct,
            taskId = launchingTask.taskId,
            minimizingTaskId = null,
        )
        mixedHandler.startAnimation(
            transition,
            createTransitionInfo(
                TRANSIT_OPEN,
                listOf(launchTaskChange)
            ),
            SurfaceControl.Transaction(),
            SurfaceControl.Transaction(),
        ) { }

        verify(rootTaskDisplayAreaOrganizer, times(0))
            .reparentToDisplayArea(anyInt(), any(), any())
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
    fun startAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
        val wct = WindowContainerTransaction()
        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
        val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
        val launchTaskChange = createChange(launchingTask)
        val minimizeChange = createChange(minimizingTask)
        val transition = Binder()
        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
            .thenReturn(transition)

        mixedHandler.startLaunchTransition(
            transitionType = TRANSIT_OPEN,
            wct = wct,
            taskId = launchingTask.taskId,
            minimizingTaskId = minimizingTask.taskId,
        )
        mixedHandler.startAnimation(
            transition,
            createTransitionInfo(
                TRANSIT_OPEN,
                listOf(launchTaskChange, minimizeChange)
            ),
            SurfaceControl.Transaction(),
            SurfaceControl.Transaction(),
        ) { }

        verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea(
            anyInt(), eq(minimizeChange.leash), any())
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
    fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
        val wct = WindowContainerTransaction()
        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
        val launchTaskChange = createChange(launchingTask)
        val transition = Binder()
        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
            .thenReturn(transition)

        mixedHandler.addPendingMixedTransition(
            PendingMixedTransition.Launch(
                transition = transition,
                launchingTask = launchingTask.taskId,
                minimizingTask = null,
                exitingImmersiveTask = null,
            )
        )
        mixedHandler.startAnimation(
            transition,
            createTransitionInfo(
                TRANSIT_OPEN,
                listOf(launchTaskChange)
            ),
            SurfaceControl.Transaction(),
            SurfaceControl.Transaction(),
        ) { }

        verify(rootTaskDisplayAreaOrganizer, times(0))
            .reparentToDisplayArea(anyInt(), any(), any())
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
    fun addPendingAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
        val wct = WindowContainerTransaction()
        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
        val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
        val launchTaskChange = createChange(launchingTask)
        val minimizeChange = createChange(minimizingTask)
        val transition = Binder()
        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
            .thenReturn(transition)

        mixedHandler.addPendingMixedTransition(
            PendingMixedTransition.Launch(
                transition = transition,
                launchingTask = launchingTask.taskId,
                minimizingTask = minimizingTask.taskId,
                exitingImmersiveTask = null,
            )
        )
        mixedHandler.startAnimation(
            transition,
            createTransitionInfo(
                TRANSIT_OPEN,
                listOf(launchTaskChange, minimizeChange)
            ),
            SurfaceControl.Transaction(),
            SurfaceControl.Transaction(),
        ) { }

        verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea(
            anyInt(), eq(minimizeChange.leash), any())
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
    fun startAndAnimateLaunchTransition_removesPendingMixedTransition() {
+7 −5
Original line number Diff line number Diff line
@@ -1413,7 +1413,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
      eq(TRANSIT_TO_FRONT),
      any(),
      eq(freeformTasks[0].taskId),
      anyOrNull()
      anyOrNull(),
      anyOrNull(),
    )).thenReturn(Binder())

    controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)
@@ -1471,7 +1472,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
    val task = createTaskInfo(1001)
    whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
    whenever(desktopMixedTransitionHandler
      .startLaunchTransition(eq(TRANSIT_OPEN), any(), eq(task.taskId), anyOrNull()))
      .startLaunchTransition(eq(TRANSIT_OPEN), any(), eq(task.taskId), anyOrNull(), anyOrNull()))
      .thenReturn(Binder())

    controller.moveTaskToFront(task.taskId, remoteTransition = null)
@@ -3692,7 +3693,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
        runOnTransitionStart = runOnStartTransit,
      ))
    whenever(desktopMixedTransitionHandler
      .startLaunchTransition(any(), any(), anyInt(), anyOrNull())).thenReturn(transition)
      .startLaunchTransition(any(), any(), anyInt(), anyOrNull(), anyOrNull()))
      .thenReturn(transition)

    controller.moveTaskToFront(task.taskId, remoteTransition = null)

@@ -3713,7 +3715,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
        runOnTransitionStart = runOnStartTransit,
      ))
    whenever(desktopMixedTransitionHandler
      .startLaunchTransition(any(), any(), eq(task.taskId), anyOrNull()))
      .startLaunchTransition(any(), any(), eq(task.taskId), anyOrNull(), anyOrNull()))
      .thenReturn(transition)

    controller.moveTaskToFront(task.taskId, remoteTransition = null)
@@ -4102,7 +4104,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
    val arg: ArgumentCaptor<WindowContainerTransaction> =
      ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
    verify(desktopMixedTransitionHandler)
      .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull())
      .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull(), anyOrNull())
    return arg.value
  }