Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +7 −2 Original line number Diff line number Diff line Loading @@ -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(); } Loading @@ -983,7 +986,9 @@ public abstract class WMShellModule { closeDesktopTaskTransitionHandler, desktopImmersiveController.get(), interactionJankMonitor, handler)); handler, shellInit, rootTaskDisplayAreaOrganizer)); } @WMSingleton Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +58 −6 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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>() Loading Loading @@ -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) { Loading @@ -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, Loading @@ -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) Loading Loading @@ -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") Loading @@ -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. Loading Loading @@ -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( Loading @@ -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, Loading Loading @@ -341,6 +392,7 @@ class DesktopMixedTransitionHandler( data class Launch( override val transition: IBinder, val launchingTask: Int, val minimizingTask: Int?, val exitingImmersiveTask: Int?, ) : PendingMixedTransition() } Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +18 −0 Original line number Diff line number Diff line Loading @@ -698,6 +698,7 @@ class DesktopTasksController( transitionType = transitionType, wct = wct, taskId = taskId, minimizingTaskId = taskIdToMinimize, exitingImmersiveTask = exitingImmersiveTask, ) taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) } Loading Loading @@ -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) Loading Loading @@ -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 Loading Loading @@ -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 ) Loading Loading @@ -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 Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +160 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading @@ -94,7 +101,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { closeDesktopTaskTransitionHandler, desktopImmersiveController, interactionJankMonitor, mockHandler mockHandler, shellInit, rootTaskDisplayAreaOrganizer, ) } Loading Loading @@ -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())) Loading Loading @@ -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() { Loading Loading @@ -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() { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +7 −5 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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) Loading Loading @@ -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) Loading @@ -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) Loading Loading @@ -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 } Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +7 −2 Original line number Diff line number Diff line Loading @@ -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(); } Loading @@ -983,7 +986,9 @@ public abstract class WMShellModule { closeDesktopTaskTransitionHandler, desktopImmersiveController.get(), interactionJankMonitor, handler)); handler, shellInit, rootTaskDisplayAreaOrganizer)); } @WMSingleton Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +58 −6 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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>() Loading Loading @@ -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) { Loading @@ -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, Loading @@ -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) Loading Loading @@ -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") Loading @@ -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. Loading Loading @@ -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( Loading @@ -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, Loading Loading @@ -341,6 +392,7 @@ class DesktopMixedTransitionHandler( data class Launch( override val transition: IBinder, val launchingTask: Int, val minimizingTask: Int?, val exitingImmersiveTask: Int?, ) : PendingMixedTransition() } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +18 −0 Original line number Diff line number Diff line Loading @@ -698,6 +698,7 @@ class DesktopTasksController( transitionType = transitionType, wct = wct, taskId = taskId, minimizingTaskId = taskIdToMinimize, exitingImmersiveTask = exitingImmersiveTask, ) taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) } Loading Loading @@ -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) Loading Loading @@ -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 Loading Loading @@ -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 ) Loading Loading @@ -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 Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +160 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading @@ -94,7 +101,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { closeDesktopTaskTransitionHandler, desktopImmersiveController, interactionJankMonitor, mockHandler mockHandler, shellInit, rootTaskDisplayAreaOrganizer, ) } Loading Loading @@ -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())) Loading Loading @@ -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() { Loading Loading @@ -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() { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +7 −5 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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) Loading Loading @@ -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) Loading @@ -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) Loading Loading @@ -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 } Loading