diff options
5 files changed, 250 insertions, 16 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 615dad31534f..817be3b1fe0d 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 @@ -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 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt index 0bc571f4782c..48bb2a8b4a74 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt @@ -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() } 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 515b203dd492..75f8839692a2 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 @@ -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 diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt index 868883d12ac0..df061e368071 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt @@ -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())) @@ -274,6 +285,24 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @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() { val wct = WindowContainerTransaction() @@ -355,6 +384,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() { val wct = WindowContainerTransaction() 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 fc89b0e127ae..b157d557c1d8 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 @@ -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 } |