diff options
| author | 2024-10-21 06:47:37 +0000 | |
|---|---|---|
| committer | 2024-10-23 06:26:46 +0000 | |
| commit | 8315ff0300ec088872acccacdff788707275cad1 (patch) | |
| tree | f0060fb81dbd60fa6462ffd03e678f6c951f9ff9 | |
| parent | 9126dc19a7766fc65be83556f93ab9ee3f5c3e00 (diff) | |
Add mixed transition handling for desktop changes
Identifies desktop changes (immersive exits for now) within transitions
that should be mainly handled by existing handlers and splits it so that
the desktop change is animated by its own handler.
Specifically, deskop immersive exits during an open/front transition
should be split so that the immersive task that is being resized is
animated by the immersive handler, while the opening task remains
animated by the DefaultTransitionHandler.
Similarly, for intent launches with a remote handler, the immersive
change is split and animated by the immersive handler and the rest is
sent to the remote handler to animate.
Flag: com.android.window.flags.enable_fully_immersive_in_desktop
Bug: 372319492
Test: while in desktop immersive, launch an app from the taskbar and see
both of them animate
Test: while in desktop immersive, launch an app from the notification
shade and see both of them animate
Change-Id: Ib63601322df20505dd7c012d85b5705d31dec5a8
9 files changed, 676 insertions, 210 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 4c2588984500..b700a5455f1a 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 @@ -67,7 +67,7 @@ import com.android.wm.shell.dagger.pip.PipModule; import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler; -import com.android.wm.shell.desktopmode.DesktopFullImmersiveTransitionHandler; +import com.android.wm.shell.desktopmode.DesktopImmersiveController; import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler; import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; @@ -397,12 +397,12 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, Transitions transitions, - Optional<DesktopFullImmersiveTransitionHandler> desktopImmersiveTransitionHandler, + Optional<DesktopImmersiveController> desktopImmersiveController, WindowDecorViewModel windowDecorViewModel, Optional<TaskChangeListener> taskChangeListener, FocusTransitionObserver focusTransitionObserver) { return new FreeformTaskTransitionObserver( - context, shellInit, transitions, desktopImmersiveTransitionHandler, + context, shellInit, transitions, desktopImmersiveController, windowDecorViewModel, taskChangeListener, focusTransitionObserver); } @@ -638,7 +638,7 @@ public abstract class WMShellModule { ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, DragToDesktopTransitionHandler dragToDesktopTransitionHandler, @DynamicOverride DesktopRepository desktopRepository, - Optional<DesktopFullImmersiveTransitionHandler> desktopFullImmersiveTransitionHandler, + Optional<DesktopImmersiveController> desktopImmersiveController, DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver, LaunchAdjacentController launchAdjacentController, RecentsTransitionHandler recentsTransitionHandler, @@ -657,7 +657,7 @@ public abstract class WMShellModule { returnToDragStartAnimator, enterDesktopTransitionHandler, exitDesktopTransitionHandler, desktopModeDragAndDropTransitionHandler, toggleResizeDesktopTaskTransitionHandler, - dragToDesktopTransitionHandler, desktopFullImmersiveTransitionHandler.get(), + dragToDesktopTransitionHandler, desktopImmersiveController.get(), desktopRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter, @@ -705,7 +705,7 @@ public abstract class WMShellModule { @WMSingleton @Provides - static Optional<DesktopFullImmersiveTransitionHandler> provideDesktopImmersiveHandler( + static Optional<DesktopImmersiveController> provideDesktopImmersiveController( Context context, Transitions transitions, @DynamicOverride DesktopRepository desktopRepository, @@ -713,7 +713,7 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer) { if (DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.of( - new DesktopFullImmersiveTransitionHandler( + new DesktopImmersiveController( transitions, desktopRepository, displayController, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt index 19ffd969cfac..d0bc5f0955f7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt @@ -36,20 +36,21 @@ import com.android.wm.shell.common.DisplayController import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionHandler +import com.android.wm.shell.transition.Transitions.TransitionObserver import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener /** - * A [TransitionHandler] to move a task in/out of desktop's full immersive state where the task + * A controller to move tasks in/out of desktop's full immersive state where the task * remains freeform while being able to take fullscreen bounds and have its App Header visibility * be transient below the status bar like in fullscreen immersive mode. */ -class DesktopFullImmersiveTransitionHandler( +class DesktopImmersiveController( private val transitions: Transitions, private val desktopRepository: DesktopRepository, private val displayController: DisplayController, private val shellTaskOrganizer: ShellTaskOrganizer, private val transactionSupplier: () -> SurfaceControl.Transaction, -) : TransitionHandler { +) : TransitionHandler, TransitionObserver { constructor( transitions: Transitions, @@ -67,7 +68,7 @@ class DesktopFullImmersiveTransitionHandler( private var state: TransitionState? = null @VisibleForTesting - val pendingExternalExitTransitions = mutableSetOf<ExternalPendingExit>() + val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>() /** Whether there is an immersive transition that hasn't completed yet. */ private val inProgress: Boolean @@ -184,6 +185,17 @@ class DesktopFullImmersiveTransitionHandler( return null } + + /** Whether the [change] in the [transition] is a known immersive change. */ + fun isImmersiveChange( + transition: IBinder, + change: TransitionInfo.Change, + ): Boolean { + return pendingExternalExitTransitions.any { + it.transition == transition && it.taskId == change.taskInfo?.taskId + } + } + private fun addPendingImmersiveExit(taskId: Int, displayId: Int, transition: IBinder) { pendingExternalExitTransitions.add( ExternalPendingExit( @@ -201,10 +213,11 @@ class DesktopFullImmersiveTransitionHandler( finishTransaction: SurfaceControl.Transaction, finishCallback: Transitions.TransitionFinishCallback ): Boolean { + logD("startAnimation transition=%s", transition) val state = requireState() if (transition != state.transition) return false animateResize( - transitionState = state, + targetTaskId = state.taskId, info = info, startTransaction = startTransaction, finishTransaction = finishTransaction, @@ -214,40 +227,55 @@ class DesktopFullImmersiveTransitionHandler( } private fun animateResize( - transitionState: TransitionState, + targetTaskId: Int, info: TransitionInfo, startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, finishCallback: Transitions.TransitionFinishCallback ) { + logD("animateResize for task#%d", targetTaskId) val change = info.changes.first { c -> val taskInfo = c.taskInfo - return@first taskInfo != null && taskInfo.taskId == transitionState.taskId + return@first taskInfo != null && taskInfo.taskId == targetTaskId } + animateResizeChange(change, startTransaction, finishTransaction, finishCallback) + } + + /** + * Animate an immersive change. + * + * As of now, both enter and exit transitions have the same animation, a veiled resize. + */ + fun animateResizeChange( + change: TransitionInfo.Change, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: Transitions.TransitionFinishCallback, + ) { + val taskId = change.taskInfo!!.taskId val leash = change.leash val startBounds = change.startAbsBounds val endBounds = change.endAbsBounds - + logD("Animating resize change for task#%d from %s to %s", taskId, startBounds, endBounds) + + startTransaction + .setPosition(leash, startBounds.left.toFloat(), startBounds.top.toFloat()) + .setWindowCrop(leash, startBounds.width(), startBounds.height()) + .show(leash) + onTaskResizeAnimationListener + ?.onAnimationStart(taskId, startTransaction, startBounds) + ?: startTransaction.apply() val updateTransaction = transactionSupplier() ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds).apply { duration = FULL_IMMERSIVE_ANIM_DURATION_MS interpolator = DecelerateInterpolator() addListener( - onStart = { - startTransaction - .setPosition(leash, startBounds.left.toFloat(), startBounds.top.toFloat()) - .setWindowCrop(leash, startBounds.width(), startBounds.height()) - .show(leash) - onTaskResizeAnimationListener - ?.onAnimationStart(transitionState.taskId, startTransaction, startBounds) - ?: startTransaction.apply() - }, onEnd = { finishTransaction .setPosition(leash, endBounds.left.toFloat(), endBounds.top.toFloat()) .setWindowCrop(leash, endBounds.width(), endBounds.height()) .apply() - onTaskResizeAnimationListener?.onAnimationEnd(transitionState.taskId) + onTaskResizeAnimationListener?.onAnimationEnd(taskId) finishCallback.onTransitionFinished(null /* wct */) clearState() } @@ -259,7 +287,7 @@ class DesktopFullImmersiveTransitionHandler( .setWindowCrop(leash, rect.width(), rect.height()) .apply() onTaskResizeAnimationListener - ?.onBoundsChange(transitionState.taskId, updateTransaction, rect) + ?.onBoundsChange(taskId, updateTransaction, rect) ?: updateTransaction.apply() } start() @@ -289,15 +317,20 @@ class DesktopFullImmersiveTransitionHandler( * |onTransitionReady|, before this transition actually animates) because drawing decorations * depends on whether the task is in full immersive state or not. */ - fun onTransitionReady(transition: IBinder, info: TransitionInfo) { + override fun onTransitionReady( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + ) { + logD("onTransitionReady transition=%s", transition) // Check if this is a pending external exit transition. val pendingExit = pendingExternalExitTransitions .firstOrNull { pendingExit -> pendingExit.transition == transition } if (pendingExit != null) { - pendingExternalExitTransitions.remove(pendingExit) if (info.hasTaskChange(taskId = pendingExit.taskId)) { if (desktopRepository.isTaskInFullImmersiveState(pendingExit.taskId)) { - logV("Pending external exit for task ${pendingExit.taskId} verified") + logV("Pending external exit for task#%d verified", pendingExit.taskId) desktopRepository.setTaskInFullImmersiveState( displayId = pendingExit.displayId, taskId = pendingExit.taskId, @@ -316,7 +349,7 @@ class DesktopFullImmersiveTransitionHandler( val state = requireState() val startBounds = info.changes.first { c -> c.taskInfo?.taskId == state.taskId } .startAbsBounds - logV("Direct move for task ${state.taskId} in ${state.direction} direction verified") + logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction) when (state.direction) { Direction.ENTER -> { desktopRepository.setTaskInFullImmersiveState( @@ -348,7 +381,7 @@ class DesktopFullImmersiveTransitionHandler( .filter { c -> desktopRepository.isTaskInFullImmersiveState(c.taskInfo!!.taskId) } .filter { c -> c.startRotation != c.endRotation } .forEach { c -> - logV("Detected immersive exit due to rotation for task: ${c.taskInfo!!.taskId}") + logV("Detected immersive exit due to rotation for task#%d", c.taskInfo!!.taskId) desktopRepository.setTaskInFullImmersiveState( displayId = c.taskInfo!!.displayId, taskId = c.taskInfo!!.taskId, @@ -357,6 +390,32 @@ class DesktopFullImmersiveTransitionHandler( } } + override fun onTransitionMerged(merged: IBinder, playing: IBinder) { + logD("onTransitionMerged merged=%s playing=%s", merged, playing) + val pendingExit = pendingExternalExitTransitions + .firstOrNull { pendingExit -> pendingExit.transition == merged } + if (pendingExit != null) { + logV( + "Pending exit transition %s for task#%s merged into %s", + merged, pendingExit.taskId, playing + ) + pendingExit.transition = playing + } + } + + override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { + logD("onTransitionFinished transition=%s aborted=%b", transition, aborted) + val pendingExit = pendingExternalExitTransitions + .firstOrNull { pendingExit -> pendingExit.transition == transition } + if (pendingExit != null) { + logV( + "Pending exit transition %s for task#%s finished", + transition, pendingExit + ) + pendingExternalExitTransitions.remove(pendingExit) + } + } + private fun clearState() { state = null } @@ -399,7 +458,7 @@ class DesktopFullImmersiveTransitionHandler( data class ExternalPendingExit( val taskId: Int, val displayId: Int, - val transition: IBinder, + var transition: IBinder, ) private enum class Direction { @@ -410,6 +469,10 @@ class DesktopFullImmersiveTransitionHandler( ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } + private fun logD(msg: String, vararg arguments: Any?) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + private companion object { private const val TAG = "DesktopImmersive" 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 18cf1f2ef4cc..781aee07a902 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 @@ -48,6 +48,7 @@ import android.view.DragEvent import android.view.KeyEvent import android.view.MotionEvent import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_NONE @@ -59,6 +60,7 @@ import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVI import android.window.DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS import android.window.RemoteTransition import android.window.TransitionInfo +import android.window.TransitionInfo.Change import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.annotation.BinderThread @@ -115,6 +117,7 @@ import com.android.wm.shell.sysui.UserChangeListener import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TransitionFinishCallback import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener @@ -146,7 +149,7 @@ class DesktopTasksController( private val desktopModeDragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler, private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler, - private val immersiveTransitionHandler: DesktopFullImmersiveTransitionHandler, + private val desktopImmersiveController: DesktopImmersiveController, private val taskRepository: DesktopRepository, private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver, private val launchAdjacentController: LaunchAdjacentController, @@ -252,7 +255,7 @@ class DesktopTasksController( toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) dragToDesktopTransitionHandler.onTaskResizeAnimationListener = listener - immersiveTransitionHandler.onTaskResizeAnimationListener = listener + desktopImmersiveController.onTaskResizeAnimationListener = listener } fun setOnTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) { @@ -372,7 +375,7 @@ class DesktopTasksController( // TODO(342378842): Instead of using default display, support multiple displays val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask( DEFAULT_DISPLAY, wct, taskId) - val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable( + val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable( wct = wct, displayId = DEFAULT_DISPLAY, excludeTaskId = taskId, @@ -403,7 +406,7 @@ class DesktopTasksController( } logV("moveRunningTaskToDesktop taskId=%d", task.taskId) exitSplitIfApplicable(wct, task) - val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable( + val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable( wct = wct, displayId = task.displayId, excludeTaskId = task.taskId, @@ -452,7 +455,7 @@ class DesktopTasksController( val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId) addMoveToDesktopChanges(wct, taskInfo) - val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable( + val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable( wct, taskInfo.displayId) val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct) transition?.let { @@ -499,7 +502,7 @@ class DesktopTasksController( taskId ) ) - return immersiveTransitionHandler.exitImmersiveIfApplicable(wct, taskInfo) + return desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo) } fun minimizeTask(taskInfo: RunningTaskInfo) { @@ -512,7 +515,7 @@ class DesktopTasksController( removeWallpaperActivity(wct) } // Notify immersive handler as it might need to exit immersive state. - val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable(wct, taskInfo) + val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo) wct.reorder(taskInfo.token, false) val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct) @@ -616,7 +619,7 @@ class DesktopTasksController( logV("moveBackgroundTaskToFront taskId=%s", taskId) val wct = WindowContainerTransaction() // TODO: b/342378842 - Instead of using default display, support multiple displays - val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable( + val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable( wct = wct, displayId = DEFAULT_DISPLAY, excludeTaskId = taskId, @@ -642,7 +645,7 @@ class DesktopTasksController( logV("moveTaskToFront taskId=%s", taskInfo.taskId) val wct = WindowContainerTransaction() wct.reorder(taskInfo.token, true /* onTop */, true /* includingParents */) - val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable( + val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable( wct = wct, displayId = taskInfo.displayId, excludeTaskId = taskInfo.taskId, @@ -749,12 +752,12 @@ class DesktopTasksController( private fun moveDesktopTaskToFullImmersive(taskInfo: RunningTaskInfo) { check(taskInfo.isFreeform) { "Task must already be in freeform" } - immersiveTransitionHandler.moveTaskToImmersive(taskInfo) + desktopImmersiveController.moveTaskToImmersive(taskInfo) } private fun exitDesktopTaskFromFullImmersive(taskInfo: RunningTaskInfo) { check(taskInfo.isFreeform) { "Task must already be in freeform" } - immersiveTransitionHandler.moveTaskToNonImmersive(taskInfo) + desktopImmersiveController.moveTaskToNonImmersive(taskInfo) } /** @@ -1233,6 +1236,67 @@ class DesktopTasksController( return result } + /** Whether the given [change] in the [transition] is a known desktop change. */ + fun isDesktopChange( + transition: IBinder, + change: TransitionInfo.Change, + ): Boolean { + // Only the immersive controller is currently involved in mixed transitions. + return Flags.enableFullyImmersiveInDesktop() + && desktopImmersiveController.isImmersiveChange(transition, change) + } + + /** + * Whether the given transition [info] will potentially include a desktop change, in which + * case the transition should be treated as mixed so that the change is in part animated by + * one of the desktop transition handlers. + */ + fun shouldPlayDesktopAnimation(info: TransitionRequestInfo): Boolean { + // Only immersive mixed transition are currently supported. + if (!Flags.enableFullyImmersiveInDesktop()) return false + val triggerTask = info.triggerTask ?: return false + if (!isDesktopModeShowing(triggerTask.displayId)) { + return false + } + if (!TransitionUtil.isOpeningType(info.type)) { + return false + } + taskRepository.getTaskInFullImmersiveState(displayId = triggerTask.displayId) + ?: return false + return when { + triggerTask.isFullscreen -> { + // Trigger fullscreen task will enter desktop, so any existing immersive task + // should exit. + shouldFullscreenTaskLaunchSwitchToDesktop(triggerTask) + } + triggerTask.isFreeform -> { + // Trigger freeform task will enter desktop, so any existing immersive task should + // exit. + !shouldFreeformTaskLaunchSwitchToFullscreen(triggerTask) + } + else -> false + } + } + + /** Animate a desktop change found in a mixed transitions. */ + fun animateDesktopChange( + transition: IBinder, + change: Change, + startTransaction: Transaction, + finishTransaction: Transaction, + finishCallback: TransitionFinishCallback, + ) { + if (!desktopImmersiveController.isImmersiveChange(transition, change)) { + throw IllegalStateException("Only immersive changes support desktop mixed transitions") + } + desktopImmersiveController.animateResizeChange( + change, + startTransaction, + finishTransaction, + finishCallback + ) + } + private fun taskContainsDragAndDropCookie(taskInfo: RunningTaskInfo?) = taskInfo?.launchCookies?.any { it == dragAndDropFullscreenCookie } ?: false @@ -1279,7 +1343,7 @@ class DesktopTasksController( wct.startTask(requestedTaskId, options.toBundle()) val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask( callingTask.displayId, wct, requestedTaskId) - val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable( + val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable( wct = wct, displayId = callingTask.displayId, excludeTaskId = requestedTaskId, @@ -1392,7 +1456,7 @@ class DesktopTasksController( return null } val wct = WindowContainerTransaction() - if (!isDesktopModeShowing(task.displayId)) { + if (shouldFreeformTaskLaunchSwitchToFullscreen(task)) { logD("Bring desktop tasks to front on transition=taskId=%d", task.taskId) if (taskRepository.isActiveTask(task.taskId) && !forceEnterDesktop(task.displayId)) { // We are outside of desktop mode and already existing desktop task is being @@ -1423,7 +1487,7 @@ class DesktopTasksController( } // Desktop Mode is showing and we're launching a new Task: // 1) Exit immersive if needed. - immersiveTransitionHandler.exitImmersiveIfApplicable(transition, wct, task.displayId) + desktopImmersiveController.exitImmersiveIfApplicable(transition, wct, task.displayId) // 2) minimize a Task if needed. val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task.taskId) if (taskToMinimize != null) { @@ -1438,7 +1502,7 @@ class DesktopTasksController( transition: IBinder ): WindowContainerTransaction? { logV("handleFullscreenTaskLaunch") - if (isDesktopModeShowing(task.displayId) || forceEnterDesktop(task.displayId)) { + if (shouldFullscreenTaskLaunchSwitchToDesktop(task)) { logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId) return WindowContainerTransaction().also { wct -> addMoveToDesktopChanges(wct, task) @@ -1454,7 +1518,7 @@ class DesktopTasksController( val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task.taskId) addPendingMinimizeTransition(transition, taskToMinimize) - immersiveTransitionHandler.exitImmersiveIfApplicable( + desktopImmersiveController.exitImmersiveIfApplicable( transition, wct, task.displayId ) } @@ -1462,6 +1526,12 @@ class DesktopTasksController( return null } + private fun shouldFreeformTaskLaunchSwitchToFullscreen(task: RunningTaskInfo): Boolean = + !isDesktopModeShowing(task.displayId) + + private fun shouldFullscreenTaskLaunchSwitchToDesktop(task: RunningTaskInfo): Boolean = + isDesktopModeShowing(task.displayId) || forceEnterDesktop(task.displayId) + /** * If a task is not compatible with desktop mode freeform, it should always be launched in * fullscreen. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java index 771573d48e45..7631ece761b5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java @@ -28,7 +28,7 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.window.flags.Flags; -import com.android.wm.shell.desktopmode.DesktopFullImmersiveTransitionHandler; +import com.android.wm.shell.desktopmode.DesktopImmersiveController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.Transitions; @@ -48,7 +48,7 @@ import java.util.Optional; */ public class FreeformTaskTransitionObserver implements Transitions.TransitionObserver { private final Transitions mTransitions; - private final Optional<DesktopFullImmersiveTransitionHandler> mImmersiveTransitionHandler; + private final Optional<DesktopImmersiveController> mDesktopImmersiveController; private final WindowDecorViewModel mWindowDecorViewModel; private final Optional<TaskChangeListener> mTaskChangeListener; private final FocusTransitionObserver mFocusTransitionObserver; @@ -60,12 +60,12 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs Context context, ShellInit shellInit, Transitions transitions, - Optional<DesktopFullImmersiveTransitionHandler> immersiveTransitionHandler, + Optional<DesktopImmersiveController> desktopImmersiveController, WindowDecorViewModel windowDecorViewModel, Optional<TaskChangeListener> taskChangeListener, FocusTransitionObserver focusTransitionObserver) { mTransitions = transitions; - mImmersiveTransitionHandler = immersiveTransitionHandler; + mDesktopImmersiveController = desktopImmersiveController; mWindowDecorViewModel = windowDecorViewModel; mTaskChangeListener = taskChangeListener; mFocusTransitionObserver = focusTransitionObserver; @@ -89,7 +89,8 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs // TODO(b/367268953): Remove when DesktopTaskListener is introduced and the repository // is updated from there **before** the |mWindowDecorViewModel| methods are invoked. // Otherwise window decoration relayout won't run with the immersive state up to date. - mImmersiveTransitionHandler.ifPresent(h -> h.onTransitionReady(transition, info)); + mDesktopImmersiveController.ifPresent(h -> + h.onTransitionReady(transition, info, startT, finishT)); } // Update focus state first to ensure the correct state can be queried from listeners. // TODO(371503964): Remove this once the unified task repository is ready. @@ -194,10 +195,20 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs } @Override - public void onTransitionStarting(@NonNull IBinder transition) {} + public void onTransitionStarting(@NonNull IBinder transition) { + if (Flags.enableFullyImmersiveInDesktop()) { + // TODO(b/367268953): Remove when DesktopTaskListener is introduced. + mDesktopImmersiveController.ifPresent(h -> h.onTransitionStarting(transition)); + } + } @Override public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) { + if (Flags.enableFullyImmersiveInDesktop()) { + // TODO(b/367268953): Remove when DesktopTaskListener is introduced. + mDesktopImmersiveController.ifPresent(h -> h.onTransitionMerged(merged, playing)); + } + final List<ActivityManager.RunningTaskInfo> infoOfMerged = mTransitionToTaskInfo.get(merged); if (infoOfMerged == null) { @@ -218,6 +229,11 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs @Override public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) { + if (Flags.enableFullyImmersiveInDesktop()) { + // TODO(b/367268953): Remove when DesktopTaskListener is introduced. + mDesktopImmersiveController.ifPresent(h -> h.onTransitionFinished(transition, aborted)); + } + final List<ActivityManager.RunningTaskInfo> taskInfo = mTransitionToTaskInfo.getOrDefault(transition, Collections.emptyList()); mTransitionToTaskInfo.remove(transition); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 766a6b3f48ac..0d89f757903e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -83,8 +83,11 @@ public class DefaultMixedHandler implements MixedTransitionHandler, /** Both the display and split-state (enter/exit) is changing */ static final int TYPE_DISPLAY_AND_SPLIT_CHANGE = 2; - /** Pip was entered while handling an intent with its own remoteTransition. */ - static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3; + /** + * While handling an intent with its own remoteTransition, a PIP enter or Desktop immersive + * exit change is found. + */ + static final int TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE = 3; /** Recents transition while split-screen foreground. */ static final int TYPE_RECENTS_DURING_SPLIT = 4; @@ -110,6 +113,9 @@ public class DefaultMixedHandler implements MixedTransitionHandler, /** The display changes when pip is entering. */ static final int TYPE_ENTER_PIP_WITH_DISPLAY_CHANGE = 11; + /** Open transition during a desktop session. */ + static final int TYPE_OPEN_IN_DESKTOP = 12; + /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; @@ -296,7 +302,7 @@ public class DefaultMixedHandler implements MixedTransitionHandler, return null; } final MixedTransition mixed = createDefaultMixedTransition( - MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition); + MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE, transition); mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); if (mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { @@ -334,6 +340,20 @@ public class DefaultMixedHandler implements MixedTransitionHandler, MixedTransition.TYPE_UNFOLD, transition)); } return wct; + } else if (mDesktopTasksController != null + && mDesktopTasksController.shouldPlayDesktopAnimation(request)) { + final Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler = + mPlayer.dispatchRequest(transition, request, /* skip= */ this); + if (handler == null) { + return null; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a desktop request, so" + + " treat it as Mixed. handler=%s", handler.first); + final MixedTransition mixed = createDefaultMixedTransition( + MixedTransition.TYPE_OPEN_IN_DESKTOP, transition); + mixed.mLeftoversHandler = handler.first; + mActiveTransitions.add(mixed); + return handler.second; } return null; } @@ -341,7 +361,7 @@ public class DefaultMixedHandler implements MixedTransitionHandler, private DefaultMixedTransition createDefaultMixedTransition(int type, IBinder transition) { return new DefaultMixedTransition( type, transition, mPlayer, this, mPipHandler, mSplitHandler, mKeyguardHandler, - mUnfoldHandler, mActivityEmbeddingController); + mUnfoldHandler, mActivityEmbeddingController, mDesktopTasksController); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java index c8921d256d7f..3d3de88cdafc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -30,6 +30,7 @@ import android.window.TransitionInfo; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; +import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -39,15 +40,19 @@ import com.android.wm.shell.unfold.UnfoldTransitionHandler; class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { private final UnfoldTransitionHandler mUnfoldHandler; private final ActivityEmbeddingController mActivityEmbeddingController; + @Nullable + private final DesktopTasksController mDesktopTasksController; DefaultMixedTransition(int type, IBinder transition, Transitions player, MixedTransitionHandler mixedHandler, PipTransitionController pipHandler, StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler, UnfoldTransitionHandler unfoldHandler, - ActivityEmbeddingController activityEmbeddingController) { + ActivityEmbeddingController activityEmbeddingController, + @Nullable DesktopTasksController desktopTasksController) { super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler); mUnfoldHandler = unfoldHandler; mActivityEmbeddingController = activityEmbeddingController; + mDesktopTasksController = desktopTasksController; switch (type) { case TYPE_UNFOLD: @@ -57,7 +62,8 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: case TYPE_ENTER_PIP_FROM_SPLIT: case TYPE_KEYGUARD: - case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: + case TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE: + case TYPE_OPEN_IN_DESKTOP: default: break; } @@ -85,11 +91,14 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { case TYPE_KEYGUARD -> animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback, mKeyguardHandler, mPipHandler); - case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE -> - animateOpenIntentWithRemoteAndPip(transition, info, startTransaction, + case TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE -> + animateOpenIntentWithRemoteAndPipOrDesktop(transition, info, startTransaction, finishTransaction, finishCallback); case TYPE_UNFOLD -> animateUnfold(info, startTransaction, finishTransaction, finishCallback); + case TYPE_OPEN_IN_DESKTOP -> + animateOpenInDesktop( + transition, info, startTransaction, finishTransaction, finishCallback); default -> throw new IllegalStateException( "Starting default mixed animation with unknown or illegal type: " + mType); }; @@ -146,31 +155,34 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { return true; } - private boolean animateOpenIntentWithRemoteAndPip( + private boolean animateOpenIntentWithRemoteAndPipOrDesktop( @NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for opening an intent" - + " with a remote transition and PIP #%d", info.getDebugId()); - boolean handledToPip = tryAnimateOpenIntentWithRemoteAndPip( + + " with a remote transition and PIP or Desktop #%d", info.getDebugId()); + boolean handledToPipOrDesktop = tryAnimateOpenIntentWithRemoteAndPipOrDesktop( info, startTransaction, finishTransaction, finishCallback); // Consume the transition on remote handler if the leftover handler already handle this // transition. And if it cannot, the transition will be handled by remote handler, so don't // consume here. - // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip - if (handledToPip && mHasRequestToRemote + // Need to check leftOverHandler as it may change in + // #animateOpenIntentWithRemoteAndPipOrDesktop + if (handledToPipOrDesktop && mHasRequestToRemote && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null); } - return handledToPip; + return handledToPipOrDesktop; } - private boolean tryAnimateOpenIntentWithRemoteAndPip( + private boolean tryAnimateOpenIntentWithRemoteAndPipOrDesktop( @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "tryAnimateOpenIntentWithRemoteAndPipOrDesktop"); TransitionInfo.Change pipChange = null; for (int i = info.getChanges().size() - 1; i >= 0; --i) { TransitionInfo.Change change = info.getChanges().get(i); @@ -183,13 +195,31 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { info.getChanges().remove(i); } } + TransitionInfo.Change desktopChange = null; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (mDesktopTasksController != null + && mDesktopTasksController.isDesktopChange(mTransition, change)) { + if (desktopChange != null) { + throw new IllegalStateException("More than 1 desktop changes in one" + + " transition? " + info); + } + desktopChange = change; + info.getChanges().remove(i); + } + } Transitions.TransitionFinishCallback finishCB = (wct) -> { --mInFlightSubAnimations; joinFinishArgs(wct); if (mInFlightSubAnimations > 0) return; finishCallback.onTransitionFinished(mFinishWCT); }; - if (pipChange == null) { + if ((pipChange == null && desktopChange == null) + || (pipChange != null && desktopChange != null)) { + // Don't split the transition. Let the leftovers handler handle it all. + // TODO: b/? - split the transition into three pieces when there's both a PIP and a + // desktop change are present. For example, during remote intent open over a desktop + // with both a PIP capable task and an immersive task. if (mLeftoversHandler != null) { mInFlightSubAnimations = 1; if (mLeftoversHandler.startAnimation( @@ -198,27 +228,52 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { } } return false; - } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Splitting PIP into a separate" - + " animation because remote-animation likely doesn't support it #%d", - info.getDebugId()); - // Split the transition into 2 parts: the pip part and the rest. - mInFlightSubAnimations = 2; - // make a new startTransaction because pip's startEnterAnimation "consumes" it so - // we need a separate one to send over to launcher. - SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + } else if (pipChange != null && desktopChange == null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Splitting PIP into a separate" + + " animation because remote-animation likely doesn't support it #%d", + info.getDebugId()); + // Split the transition into 2 parts: the pip part and the rest. + mInFlightSubAnimations = 2; + // make a new startTransaction because pip's startEnterAnimation "consumes" it so + // we need a separate one to send over to launcher. + SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + + mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB); + + // Dispatch the rest of the transition normally. + if (mLeftoversHandler != null + && mLeftoversHandler.startAnimation(mTransition, info, + startTransaction, finishTransaction, finishCB)) { + return true; + } + mLeftoversHandler = mPlayer.dispatchTransition( + mTransition, info, startTransaction, finishTransaction, finishCB, + mMixedHandler); + return true; + } else if (pipChange == null && desktopChange != null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Splitting desktop change into a" + + "separate animation because remote-animation likely doesn't support" + + "it #%d", info.getDebugId()); + mInFlightSubAnimations = 2; + SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); - mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB); + mDesktopTasksController.animateDesktopChange( + mTransition, desktopChange, otherStartT, finishTransaction, finishCB); - // Dispatch the rest of the transition normally. - if (mLeftoversHandler != null - && mLeftoversHandler.startAnimation(mTransition, info, - startTransaction, finishTransaction, finishCB)) { + // Dispatch the rest of the transition normally. + if (mLeftoversHandler != null + && mLeftoversHandler.startAnimation(mTransition, info, + startTransaction, finishTransaction, finishCB)) { + return true; + } + mLeftoversHandler = mPlayer.dispatchTransition( + mTransition, info, startTransaction, finishTransaction, finishCB, + mMixedHandler); return true; + } else { + throw new IllegalStateException( + "All PIP and Immersive combinations should've been handled"); } - mLeftoversHandler = mPlayer.dispatchTransition( - mTransition, info, startTransaction, finishTransaction, finishCB, mMixedHandler); - return true; } private boolean animateUnfold( @@ -246,6 +301,51 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { mTransition, info, startTransaction, finishTransaction, finishCB); } + private boolean animateOpenInDesktop( + @NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "animateOpenInDesktop"); + TransitionInfo.Change desktopChange = null; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (mDesktopTasksController.isDesktopChange(mTransition, change)) { + if (desktopChange != null) { + throw new IllegalStateException("More than 1 desktop changes in one" + + " transition? " + info); + } + desktopChange = change; + info.getChanges().remove(i); + } + } + final Transitions.TransitionFinishCallback finishCB = (wct) -> { + --mInFlightSubAnimations; + joinFinishArgs(wct); + if (mInFlightSubAnimations > 0) return; + finishCallback.onTransitionFinished(mFinishWCT); + }; + if (desktopChange == null) { + if (mLeftoversHandler != null) { + mInFlightSubAnimations = 1; + if (mLeftoversHandler.startAnimation( + mTransition, info, startTransaction, finishTransaction, finishCB)) { + return true; + } + } + return false; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Splitting desktop change into a" + + "separate animation #%d", info.getDebugId()); + mInFlightSubAnimations = 2; + mDesktopTasksController.animateDesktopChange( + transition, desktopChange, startTransaction, finishTransaction, finishCB); + mLeftoversHandler = mPlayer.dispatchTransition( + mTransition, info, startTransaction, finishTransaction, finishCB, mMixedHandler); + return true; + } + @Override void mergeAnimation( @NonNull IBinder transition, @NonNull TransitionInfo info, @@ -279,7 +379,7 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { case TYPE_KEYGUARD: mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); return; - case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: + case TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE: mPipHandler.end(); if (mLeftoversHandler != null) { mLeftoversHandler.mergeAnimation( @@ -289,6 +389,10 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { case TYPE_UNFOLD: mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); return; + case TYPE_OPEN_IN_DESKTOP: + mDesktopTasksController.mergeAnimation( + transition, info, t, mergeTarget, finishCallback); + return; default: throw new IllegalStateException("Playing a default mixed transition with unknown or" + " illegal type: " + mType); @@ -310,12 +414,14 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { case TYPE_KEYGUARD: mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT); break; - case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: + case TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE: mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); break; case TYPE_UNFOLD: mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT); break; + case TYPE_OPEN_IN_DESKTOP: + mDesktopTasksController.onTransitionConsumed(transition, aborted, finishT); default: break; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt index 5842dfaef0fe..e83f5c7a79a1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFullImmersiveTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt @@ -15,6 +15,7 @@ */ package com.android.wm.shell.desktopmode +import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS import android.graphics.Rect import android.os.Binder @@ -58,13 +59,13 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever /** - * Tests for [DesktopFullImmersiveTransitionHandler]. + * Tests for [DesktopImmersiveController]. * - * Usage: atest WMShellUnitTests:DesktopFullImmersiveTransitionHandlerTest + * Usage: atest WMShellUnitTests:DesktopImmersiveControllerTest */ @SmallTest @RunWith(AndroidTestingRunner::class) -class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { +class DesktopImmersiveControllerTest : ShellTestCase() { @JvmField @Rule val setFlagsRule = SetFlagsRule() @@ -75,7 +76,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { @Mock private lateinit var mockDisplayLayout: DisplayLayout private val transactionSupplier = { SurfaceControl.Transaction() } - private lateinit var immersiveHandler: DesktopFullImmersiveTransitionHandler + private lateinit var controller: DesktopImmersiveController @Before fun setUp() { @@ -87,7 +88,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { invocation -> (invocation.getArgument(0) as Rect).set(STABLE_BOUNDS) } - immersiveHandler = DesktopFullImmersiveTransitionHandler( + controller = DesktopImmersiveController( transitions = mockTransitions, desktopRepository = desktopRepository, displayController = mockDisplayController, @@ -100,7 +101,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { fun enterImmersive_transitionReady_updatesRepository() { val task = createFreeformTask() val mockBinder = mock(IBinder::class.java) - whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler))) + whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) .thenReturn(mockBinder) desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, @@ -108,16 +109,14 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = false ) - immersiveHandler.moveTaskToImmersive(task) - immersiveHandler.onTransitionReady( + controller.moveTaskToImmersive(task) + controller.onTransitionReady( transition = mockBinder, info = createTransitionInfo( - changes = listOf( - TransitionInfo.Change(task.token, SurfaceControl()).apply { - taskInfo = task - } - ) - ) + changes = listOf(createChange(task)) + ), + startTransaction = SurfaceControl.Transaction(), + finishTransaction = SurfaceControl.Transaction(), ) assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isTrue() @@ -128,7 +127,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { fun enterImmersive_savesPreImmersiveBounds() { val task = createFreeformTask() val mockBinder = mock(IBinder::class.java) - whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler))) + whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) .thenReturn(mockBinder) desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, @@ -137,16 +136,14 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { ) assertThat(desktopRepository.removeBoundsBeforeFullImmersive(task.taskId)).isNull() - immersiveHandler.moveTaskToImmersive(task) - immersiveHandler.onTransitionReady( + controller.moveTaskToImmersive(task) + controller.onTransitionReady( transition = mockBinder, info = createTransitionInfo( - changes = listOf( - TransitionInfo.Change(task.token, SurfaceControl()).apply { - taskInfo = task - } - ) - ) + changes = listOf(createChange(task)) + ), + startTransaction = SurfaceControl.Transaction(), + finishTransaction = SurfaceControl.Transaction(), ) assertThat(desktopRepository.removeBoundsBeforeFullImmersive(task.taskId)).isNotNull() @@ -156,7 +153,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { fun exitImmersive_transitionReady_updatesRepository() { val task = createFreeformTask() val mockBinder = mock(IBinder::class.java) - whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler))) + whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) .thenReturn(mockBinder) desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, @@ -164,16 +161,14 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = true ) - immersiveHandler.moveTaskToNonImmersive(task) - immersiveHandler.onTransitionReady( + controller.moveTaskToNonImmersive(task) + controller.onTransitionReady( transition = mockBinder, info = createTransitionInfo( - changes = listOf( - TransitionInfo.Change(task.token, SurfaceControl()).apply { - taskInfo = task - } - ) - ) + changes = listOf(createChange(task)) + ), + startTransaction = SurfaceControl.Transaction(), + finishTransaction = SurfaceControl.Transaction(), ) assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse() @@ -184,7 +179,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { fun exitImmersive_onTransitionReady_removesBoundsBeforeImmersive() { val task = createFreeformTask() val mockBinder = mock(IBinder::class.java) - whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler))) + whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) .thenReturn(mockBinder) desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, @@ -193,16 +188,14 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { ) desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, Rect(100, 100, 600, 600)) - immersiveHandler.moveTaskToNonImmersive(task) - immersiveHandler.onTransitionReady( + controller.moveTaskToNonImmersive(task) + controller.onTransitionReady( transition = mockBinder, info = createTransitionInfo( - changes = listOf( - TransitionInfo.Change(task.token, SurfaceControl()).apply { - taskInfo = task - } - ) - ) + changes = listOf(createChange(task)) + ), + startTransaction = SurfaceControl.Transaction(), + finishTransaction = SurfaceControl.Transaction(), ) assertThat(desktopRepository.removeBoundsBeforeMaximize(task.taskId)).isNull() @@ -217,16 +210,15 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = true ) - immersiveHandler.onTransitionReady( + controller.onTransitionReady( transition = mock(IBinder::class.java), info = createTransitionInfo( - changes = listOf( - TransitionInfo.Change(task.token, SurfaceControl()).apply { - taskInfo = task - setRotation(/* start= */ Surface.ROTATION_0, /* end= */ Surface.ROTATION_90) - } - ) - ) + changes = listOf(createChange(task).apply { + setRotation(/* start= */ Surface.ROTATION_0, /* end= */ Surface.ROTATION_90) + }) + ), + startTransaction = SurfaceControl.Transaction(), + finishTransaction = SurfaceControl.Transaction(), ) assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse() @@ -236,28 +228,28 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { fun enterImmersive_inProgress_ignores() { val task = createFreeformTask() val mockBinder = mock(IBinder::class.java) - whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler))) + whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) .thenReturn(mockBinder) - immersiveHandler.moveTaskToImmersive(task) - immersiveHandler.moveTaskToImmersive(task) + controller.moveTaskToImmersive(task) + controller.moveTaskToImmersive(task) verify(mockTransitions, times(1)) - .startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler)) + .startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)) } @Test fun exitImmersive_inProgress_ignores() { val task = createFreeformTask() val mockBinder = mock(IBinder::class.java) - whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler))) + whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) .thenReturn(mockBinder) - immersiveHandler.moveTaskToNonImmersive(task) - immersiveHandler.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task) verify(mockTransitions, times(1)) - .startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler)) + .startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)) } @Test @@ -273,9 +265,9 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = true ) - immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) - assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit -> + assertThat(controller.pendingExternalExitTransitions.any { exit -> exit.transition == transition && exit.displayId == DEFAULT_DISPLAY && exit.taskId == task.taskId }).isTrue() @@ -294,9 +286,9 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = false ) - immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) - assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit -> + assertThat(controller.pendingExternalExitTransitions.any { exit -> exit.transition == transition && exit.displayId == DEFAULT_DISPLAY && exit.taskId == task.taskId }).isFalse() @@ -315,7 +307,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = true ) - immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) assertThat(wct.hasBoundsChange(task.token)).isTrue() } @@ -333,7 +325,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = false ) - immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) assertThat(wct.hasBoundsChange(task.token)).isFalse() } @@ -351,13 +343,13 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = true ) - immersiveHandler.exitImmersiveIfApplicable( + controller.exitImmersiveIfApplicable( wct = wct, displayId = DEFAULT_DISPLAY, excludeTaskId = task.taskId )?.invoke(transition) - assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit -> + assertThat(controller.pendingExternalExitTransitions.any { exit -> exit.transition == transition && exit.displayId == DEFAULT_DISPLAY && exit.taskId == task.taskId }).isFalse() @@ -375,7 +367,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = true ) - immersiveHandler.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task) assertThat(wct.hasBoundsChange(task.token)).isTrue() } @@ -392,7 +384,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = false ) - immersiveHandler.exitImmersiveIfApplicable(wct, task) + controller.exitImmersiveIfApplicable(wct, task) assertThat(wct.hasBoundsChange(task.token)).isFalse() } @@ -410,9 +402,9 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = true ) - immersiveHandler.exitImmersiveIfApplicable(wct, task)?.invoke(transition) + controller.exitImmersiveIfApplicable(wct, task)?.invoke(transition) - assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit -> + assertThat(controller.pendingExternalExitTransitions.any { exit -> exit.transition == transition && exit.displayId == DEFAULT_DISPLAY && exit.taskId == task.taskId }).isTrue() @@ -431,9 +423,9 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = false ) - immersiveHandler.exitImmersiveIfApplicable(wct, task)?.invoke(transition) + controller.exitImmersiveIfApplicable(wct, task)?.invoke(transition) - assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit -> + assertThat(controller.pendingExternalExitTransitions.any { exit -> exit.transition == transition && exit.displayId == DEFAULT_DISPLAY && exit.taskId == task.taskId }).isFalse() @@ -441,7 +433,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun onTransitionReady_pendingExit_removesPendingExit() { + fun onTransitionReady_pendingExit_removesPendingExitOnFinish() { val task = createFreeformTask() whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) val wct = WindowContainerTransaction() @@ -451,18 +443,19 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { taskId = task.taskId, immersive = true ) - immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) - immersiveHandler.onTransitionReady( + controller.onTransitionReady( transition = transition, info = createTransitionInfo( - changes = listOf( - TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task } - ) - ) + changes = listOf(createChange(task)) + ), + startTransaction = SurfaceControl.Transaction(), + finishTransaction = SurfaceControl.Transaction(), ) + controller.onTransitionFinished(transition, aborted = false) - assertThat(immersiveHandler.pendingExternalExitTransitions.any { exit -> + assertThat(controller.pendingExternalExitTransitions.any { exit -> exit.transition == transition && exit.displayId == DEFAULT_DISPLAY && exit.taskId == task.taskId }).isFalse() @@ -470,6 +463,42 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun onTransitionReady_pendingExit_withMerge_removesPendingExitOnFinish() { + val task = createFreeformTask() + whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + val wct = WindowContainerTransaction() + val transition = Binder() + val mergedToTransition = Binder() + desktopRepository.setTaskInFullImmersiveState( + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + immersive = true + ) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + + controller.onTransitionReady( + transition = transition, + info = createTransitionInfo( + changes = listOf(createChange(task)) + ), + startTransaction = SurfaceControl.Transaction(), + finishTransaction = SurfaceControl.Transaction(), + ) + controller.onTransitionMerged(transition, mergedToTransition) + controller.onTransitionFinished(mergedToTransition, aborted = false) + + assertThat(controller.pendingExternalExitTransitions.any { exit -> + exit.transition == transition && exit.displayId == DEFAULT_DISPLAY + && exit.taskId == task.taskId + }).isFalse() + assertThat(controller.pendingExternalExitTransitions.any { exit -> + exit.transition == mergedToTransition && exit.displayId == DEFAULT_DISPLAY + && exit.taskId == task.taskId + }).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun onTransitionReady_pendingExit_updatesRepository() { val task = createFreeformTask() whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) @@ -480,15 +509,15 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { taskId = task.taskId, immersive = true ) - immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) - immersiveHandler.onTransitionReady( + controller.onTransitionReady( transition = transition, info = createTransitionInfo( - changes = listOf( - TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task } - ) - ) + changes = listOf(createChange(task)) + ), + startTransaction = SurfaceControl.Transaction(), + finishTransaction = SurfaceControl.Transaction(), ) assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse() @@ -510,15 +539,15 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = true ) desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, Rect(100, 100, 600, 600)) - immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) - immersiveHandler.onTransitionReady( + controller.onTransitionReady( transition = transition, info = createTransitionInfo( - changes = listOf( - TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task } - ) - ) + changes = listOf(createChange(task)) + ), + startTransaction = SurfaceControl.Transaction(), + finishTransaction = SurfaceControl.Transaction(), ) assertThat(desktopRepository.removeBoundsBeforeMaximize(task.taskId)).isNull() @@ -537,7 +566,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = true ) - immersiveHandler.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task) assertThat( wct.hasBoundsChange(task.token, calculateMaximizeBounds(mockDisplayLayout, task)) @@ -561,7 +590,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { val preImmersiveBounds = Rect(100, 100, 500, 500) desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, preImmersiveBounds) - immersiveHandler.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task) assertThat( wct.hasBoundsChange(task.token, preImmersiveBounds) @@ -584,7 +613,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { immersive = true ) - immersiveHandler.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task) assertThat( wct.hasBoundsChange(task.token, calculateInitialBounds(mockDisplayLayout, task)) @@ -602,13 +631,32 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { taskId = task.taskId, immersive = true ) - immersiveHandler.exitImmersiveIfApplicable(wct, task)?.invoke(Binder()) + controller.exitImmersiveIfApplicable(wct, task)?.invoke(Binder()) - immersiveHandler.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task) verify(mockTransitions, never()).startTransition(any(), any(), any()) } + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun exitImmersiveIfApplicable_inImmersive_isImmersiveChange() { + val task = createFreeformTask() + whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + val wct = WindowContainerTransaction() + val transition = Binder() + val change = createChange(task) + desktopRepository.setTaskInFullImmersiveState( + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + immersive = true + ) + + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + + assertThat(controller.isImmersiveChange(transition, change)).isTrue() + } + private fun createTransitionInfo( @TransitionType type: Int = TRANSIT_CHANGE, @TransitionFlags flags: Int = 0, @@ -617,6 +665,11 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() { changes.forEach { change -> addChange(change) } } + private fun createChange(task: RunningTaskInfo): TransitionInfo.Change = + TransitionInfo.Change(task.token, SurfaceControl()).apply { + taskInfo = task + } + private fun WindowContainerTransaction.hasBoundsChange(token: WindowContainerToken): Boolean = this.changes.any { change -> change.key == token.asBinder() 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 7a3d44bded8e..bc2b36ccd835 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 @@ -201,7 +201,7 @@ class DesktopTasksControllerTest : ShellTestCase() { lateinit var toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler @Mock - lateinit var mockDesktopFullImmersiveTransitionHandler: DesktopFullImmersiveTransitionHandler + lateinit var mMockDesktopImmersiveController: DesktopImmersiveController @Mock lateinit var launchAdjacentController: LaunchAdjacentController @Mock lateinit var splitScreenController: SplitScreenController @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler @@ -322,7 +322,7 @@ class DesktopTasksControllerTest : ShellTestCase() { dragAndDropTransitionHandler, toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, - mockDesktopFullImmersiveTransitionHandler, + mMockDesktopImmersiveController, taskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, @@ -1773,7 +1773,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.minimizeTask(task) - verify(mockDesktopFullImmersiveTransitionHandler).exitImmersiveIfApplicable(any(), eq(task)) + verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task)) } @Test @@ -1783,7 +1783,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnTransit = RunOnStartTransitionCallback() whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(transition) - whenever(mockDesktopFullImmersiveTransitionHandler.exitImmersiveIfApplicable(any(), eq(task))) + whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task))) .thenReturn(runOnTransit) controller.minimizeTask(task) @@ -3092,13 +3092,13 @@ class DesktopTasksControllerTest : ShellTestCase() { val transition = Binder() whenever(transitions.startTransition(eq(TRANSIT_OPEN), any(), anyOrNull())) .thenReturn(transition) - whenever(mockDesktopFullImmersiveTransitionHandler + whenever(mMockDesktopImmersiveController .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId))) .thenReturn(runOnStartTransit) runOpenInstance(immersiveTask, freeformTask.taskId) - verify(mockDesktopFullImmersiveTransitionHandler) + verify(mMockDesktopImmersiveController) .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId)) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3446,7 +3446,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.toggleDesktopTaskFullImmersiveState(task) - verify(mockDesktopFullImmersiveTransitionHandler).moveTaskToImmersive(task) + verify(mMockDesktopImmersiveController).moveTaskToImmersive(task) } @Test @@ -3456,7 +3456,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.toggleDesktopTaskFullImmersiveState(task) - verify(mockDesktopFullImmersiveTransitionHandler).moveTaskToNonImmersive(task) + verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(task) } @Test @@ -3468,7 +3468,7 @@ class DesktopTasksControllerTest : ShellTestCase() { task.requestedVisibleTypes = WindowInsets.Type.statusBars() controller.onTaskInfoChanged(task) - verify(mockDesktopFullImmersiveTransitionHandler).moveTaskToNonImmersive(task) + verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(task) } @Test @@ -3480,7 +3480,7 @@ class DesktopTasksControllerTest : ShellTestCase() { task.requestedVisibleTypes = WindowInsets.Type.statusBars() controller.onTaskInfoChanged(task) - verify(mockDesktopFullImmersiveTransitionHandler, never()).moveTaskToNonImmersive(task) + verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(task) } @Test @@ -3489,13 +3489,13 @@ class DesktopTasksControllerTest : ShellTestCase() { val wct = WindowContainerTransaction() val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() - whenever(mockDesktopFullImmersiveTransitionHandler + whenever(mMockDesktopImmersiveController .exitImmersiveIfApplicable(wct, task.displayId, task.taskId)).thenReturn(runOnStartTransit) whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition) controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN) - verify(mockDesktopFullImmersiveTransitionHandler) + verify(mMockDesktopImmersiveController) .exitImmersiveIfApplicable(wct, task.displayId, task.taskId) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3506,13 +3506,13 @@ class DesktopTasksControllerTest : ShellTestCase() { val wct = WindowContainerTransaction() val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() - whenever(mockDesktopFullImmersiveTransitionHandler + whenever(mMockDesktopImmersiveController .exitImmersiveIfApplicable(wct, task.displayId, task.taskId)).thenReturn(runOnStartTransit) whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition) controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN) - verify(mockDesktopFullImmersiveTransitionHandler) + verify(mMockDesktopImmersiveController) .exitImmersiveIfApplicable(wct, task.displayId, task.taskId) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3522,14 +3522,14 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFreeformTask(background = true) val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() - whenever(mockDesktopFullImmersiveTransitionHandler + whenever(mMockDesktopImmersiveController .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId))) .thenReturn(runOnStartTransit) whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition) controller.moveTaskToFront(task.taskId, remoteTransition = null) - verify(mockDesktopFullImmersiveTransitionHandler) + verify(mMockDesktopImmersiveController) .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId)) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3539,14 +3539,14 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFreeformTask(background = false) val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() - whenever(mockDesktopFullImmersiveTransitionHandler + whenever(mMockDesktopImmersiveController .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId))) .thenReturn(runOnStartTransit) whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition) controller.moveTaskToFront(task.taskId, remoteTransition = null) - verify(mockDesktopFullImmersiveTransitionHandler) + verify(mMockDesktopImmersiveController) .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId)) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3560,7 +3560,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.handleRequest(binder, createTransition(task)) - verify(mockDesktopFullImmersiveTransitionHandler) + verify(mMockDesktopImmersiveController) .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId)) } @@ -3572,10 +3572,117 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.handleRequest(binder, createTransition(task)) - verify(mockDesktopFullImmersiveTransitionHandler) + verify(mMockDesktopImmersiveController) .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId)) } + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_notShowingDesktop_doesNotPlay() { + val triggerTask = setUpFullscreenTask(displayId = 5) + taskRepository.setTaskInFullImmersiveState( + displayId = triggerTask.displayId, + taskId = triggerTask.taskId, + immersive = true + ) + + assertThat(controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) + )).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_notOpening_doesNotPlay() { + val triggerTask = setUpFreeformTask(displayId = 5) + taskRepository.setTaskInFullImmersiveState( + displayId = triggerTask.displayId, + taskId = triggerTask.taskId, + immersive = true + ) + + assertThat(controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_CHANGE, triggerTask, /* remoteTransition= */ null) + )).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_notImmersive_doesNotPlay() { + val triggerTask = setUpFreeformTask(displayId = 5) + taskRepository.setTaskInFullImmersiveState( + displayId = triggerTask.displayId, + taskId = triggerTask.taskId, + immersive = false + ) + + assertThat(controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) + )).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_fullscreenEntersDesktop_plays() { + // At least one freeform task to be in a desktop. + val existingTask = setUpFreeformTask(displayId = 5) + val triggerTask = setUpFullscreenTask(displayId = 5) + assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue() + taskRepository.setTaskInFullImmersiveState( + displayId = existingTask.displayId, + taskId = existingTask.taskId, + immersive = true + ) + + assertThat( + controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) + ) + ).isTrue() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_fullscreenStaysFullscreen_doesNotPlay() { + val triggerTask = setUpFullscreenTask(displayId = 5) + assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse() + + assertThat(controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) + )).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_freeformStaysInDesktop_plays() { + // At least one freeform task to be in a desktop. + val existingTask = setUpFreeformTask(displayId = 5) + val triggerTask = setUpFreeformTask(displayId = 5, active = false) + assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue() + taskRepository.setTaskInFullImmersiveState( + displayId = existingTask.displayId, + taskId = existingTask.taskId, + immersive = true + ) + + assertThat( + controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) + ) + ).isTrue() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_freeformExitsDesktop_doesNotPlay() { + val triggerTask = setUpFreeformTask(displayId = 5, active = false) + assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse() + + assertThat(controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) + )).isFalse() + } + private class RunOnStartTransitionCallback : ((IBinder) -> Unit) { var invocations = 0 private set diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java index 7ae0bcd13681..90ab2b8285cd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java @@ -43,7 +43,7 @@ import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; import com.android.window.flags.Flags; -import com.android.wm.shell.desktopmode.DesktopFullImmersiveTransitionHandler; +import com.android.wm.shell.desktopmode.DesktopImmersiveController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.TransitionInfoBuilder; @@ -70,7 +70,7 @@ public class FreeformTaskTransitionObserverTest { @Mock private Transitions mTransitions; @Mock - private DesktopFullImmersiveTransitionHandler mDesktopFullImmersiveTransitionHandler; + private DesktopImmersiveController mDesktopImmersiveController; @Mock private WindowDecorViewModel mWindowDecorViewModel; @Mock @@ -92,7 +92,7 @@ public class FreeformTaskTransitionObserverTest { mTransitionObserver = new FreeformTaskTransitionObserver( context, mShellInit, mTransitions, - Optional.of(mDesktopFullImmersiveTransitionHandler), + Optional.of(mDesktopImmersiveController), mWindowDecorViewModel, Optional.of(mTaskChangeListener), mFocusTransitionObserver); final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass( @@ -321,7 +321,7 @@ public class FreeformTaskTransitionObserverTest { @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - public void onTransitionReady_forwardsToDesktopImmersiveHandler() { + public void onTransitionReady_forwardsToDesktopImmersiveController() { final IBinder transition = mock(IBinder.class); final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CHANGE, 0).build(); final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); @@ -329,7 +329,38 @@ public class FreeformTaskTransitionObserverTest { mTransitionObserver.onTransitionReady(transition, info, startT, finishT); - verify(mDesktopFullImmersiveTransitionHandler).onTransitionReady(transition, info); + verify(mDesktopImmersiveController).onTransitionReady(transition, info, startT, finishT); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + public void onTransitionMerged_forwardsToDesktopImmersiveController() { + final IBinder merged = mock(IBinder.class); + final IBinder playing = mock(IBinder.class); + + mTransitionObserver.onTransitionMerged(merged, playing); + + verify(mDesktopImmersiveController).onTransitionMerged(merged, playing); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + public void onTransitionStarting_forwardsToDesktopImmersiveController() { + final IBinder transition = mock(IBinder.class); + + mTransitionObserver.onTransitionStarting(transition); + + verify(mDesktopImmersiveController).onTransitionStarting(transition); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + public void onTransitionFinished_forwardsToDesktopImmersiveController() { + final IBinder transition = mock(IBinder.class); + + mTransitionObserver.onTransitionFinished(transition, /* aborted= */ false); + + verify(mDesktopImmersiveController).onTransitionFinished(transition, /* aborted= */ false); } private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) { |