diff options
2 files changed, 192 insertions, 163 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt index 8e2a412764eb..536dc2a58534 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt @@ -74,13 +74,11 @@ class DesktopImmersiveController( { SurfaceControl.Transaction() }, ) - @VisibleForTesting var state: TransitionState? = null - - @VisibleForTesting val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>() + @VisibleForTesting val pendingImmersiveTransitions = mutableListOf<PendingTransition>() /** Whether there is an immersive transition that hasn't completed yet. */ private val inProgress: Boolean - get() = state != null || pendingExternalExitTransitions.isNotEmpty() + get() = pendingImmersiveTransitions.isNotEmpty() private val rectEvaluator = RectEvaluator() @@ -101,20 +99,19 @@ class DesktopImmersiveController( if (inProgress) { logV( "Cannot start entry because transition(s) already in progress: %s", - getRunningTransitions(), + pendingImmersiveTransitions, ) return } val wct = WindowContainerTransaction().apply { setBounds(taskInfo.token, Rect()) } logV("Moving task ${taskInfo.taskId} into immersive mode") val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this) - state = - TransitionState( - transition = transition, - displayId = taskInfo.displayId, - taskId = taskInfo.taskId, - direction = Direction.ENTER, - ) + addPendingImmersiveTransition( + taskId = taskInfo.taskId, + displayId = taskInfo.displayId, + direction = Direction.ENTER, + transition = transition, + ) } /** Starts a transition to move an immersive task out of immersive. */ @@ -123,7 +120,7 @@ class DesktopImmersiveController( if (inProgress) { logV( "Cannot start exit because transition(s) already in progress: %s", - getRunningTransitions(), + pendingImmersiveTransitions, ) return } @@ -134,13 +131,12 @@ class DesktopImmersiveController( } logV("Moving task %d out of immersive mode, reason: %s", taskInfo.taskId, reason) val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this) - state = - TransitionState( - transition = transition, - displayId = taskInfo.displayId, - taskId = taskInfo.taskId, - direction = Direction.EXIT, - ) + addPendingImmersiveTransition( + taskId = taskInfo.taskId, + displayId = taskInfo.displayId, + direction = Direction.EXIT, + transition = transition, + ) } /** @@ -194,7 +190,13 @@ class DesktopImmersiveController( return ExitResult.Exit( exitingTask = immersiveTask, runOnTransitionStart = { transition -> - addPendingImmersiveExit(immersiveTask, displayId, transition) + addPendingImmersiveTransition( + taskId = immersiveTask, + displayId = displayId, + direction = Direction.EXIT, + transition = transition, + animate = false, + ) }, ) } @@ -220,10 +222,12 @@ class DesktopImmersiveController( return ExitResult.Exit( exitingTask = taskInfo.taskId, runOnTransitionStart = { transition -> - addPendingImmersiveExit( + addPendingImmersiveTransition( taskId = taskInfo.taskId, displayId = taskInfo.displayId, + direction = Direction.EXIT, transition = transition, + animate = false, ) }, ) @@ -233,14 +237,26 @@ class DesktopImmersiveController( /** Whether the [change] in the [transition] is a known immersive change. */ fun isImmersiveChange(transition: IBinder, change: TransitionInfo.Change): Boolean { - return pendingExternalExitTransitions.any { + return pendingImmersiveTransitions.any { it.transition == transition && it.taskId == change.taskInfo?.taskId } } - private fun addPendingImmersiveExit(taskId: Int, displayId: Int, transition: IBinder) { - pendingExternalExitTransitions.add( - ExternalPendingExit(taskId = taskId, displayId = displayId, transition = transition) + private fun addPendingImmersiveTransition( + taskId: Int, + displayId: Int, + direction: Direction, + transition: IBinder, + animate: Boolean = true, + ) { + pendingImmersiveTransitions.add( + PendingTransition( + taskId = taskId, + displayId = displayId, + direction = direction, + transition = transition, + animate = animate, + ) ) } @@ -251,19 +267,17 @@ class DesktopImmersiveController( finishTransaction: SurfaceControl.Transaction, finishCallback: Transitions.TransitionFinishCallback, ): Boolean { - val state = requireState() - check(state.transition == transition) { - "Transition $transition did not match expected state=$state" - } + val immersiveTransition = getImmersiveTransition(transition) ?: return false + if (!immersiveTransition.animate) return false logD("startAnimation transition=%s", transition) animateResize( - targetTaskId = state.taskId, + targetTaskId = immersiveTransition.taskId, info = info, startTransaction = startTransaction, finishTransaction = finishTransaction, finishCallback = { finishCallback.onTransitionFinished(/* wct= */ null) - clearState() + pendingImmersiveTransitions.remove(immersiveTransition) }, ) return true @@ -346,18 +360,6 @@ class DesktopImmersiveController( request: TransitionRequestInfo, ): WindowContainerTransaction? = null - override fun onTransitionConsumed( - transition: IBinder, - aborted: Boolean, - finishTransaction: SurfaceControl.Transaction?, - ) { - val state = this.state ?: return - if (transition == state.transition && aborted) { - clearState() - } - super.onTransitionConsumed(transition, aborted, finishTransaction) - } - /** * Called when any transition in the system is ready to play. This is needed to update the * repository state before window decorations are drawn (which happens immediately after @@ -371,67 +373,42 @@ class DesktopImmersiveController( finishTransaction: SurfaceControl.Transaction, ) { val desktopRepository: DesktopRepository = desktopUserRepositories.current - // Check if this is a pending external exit transition. - val pendingExit = - pendingExternalExitTransitions.firstOrNull { pendingExit -> - pendingExit.transition == transition - } - if (pendingExit != null) { - if (info.hasTaskChange(taskId = pendingExit.taskId)) { - if (desktopRepository.isTaskInFullImmersiveState(pendingExit.taskId)) { - logV("Pending external exit for task#%d verified", pendingExit.taskId) - desktopRepository.setTaskInFullImmersiveState( - displayId = pendingExit.displayId, - taskId = pendingExit.taskId, - immersive = false, - ) - if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { - desktopRepository.removeBoundsBeforeFullImmersive(pendingExit.taskId) - } - } - } - return - } + val pendingTransition = getImmersiveTransition(transition) - // Check if this is a direct immersive enter/exit transition. - if (transition == state?.transition) { - val state = requireState() - val immersiveChange = - info.changes.firstOrNull { c -> c.taskInfo?.taskId == state.taskId } + if (pendingTransition != null) { + val taskId = pendingTransition.taskId + val immersiveChange = info.getTaskChange(taskId = taskId) if (immersiveChange == null) { logV( - "Direct move for task#%d in %s direction missing immersive change.", - state.taskId, - state.direction, + "Transition for task#%d in %s direction missing immersive change.", + taskId, + pendingTransition.direction, ) return } - val startBounds = immersiveChange.startAbsBounds - logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction) - - when (state.direction) { - Direction.ENTER -> { - desktopRepository.setTaskInFullImmersiveState( - displayId = state.displayId, - taskId = state.taskId, - immersive = true, - ) - if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { - desktopRepository.saveBoundsBeforeFullImmersive(state.taskId, startBounds) + logV( + "Immersive transition for task#%d in %s direction verified", + taskId, + pendingTransition.direction, + ) + desktopRepository.setTaskInFullImmersiveState( + displayId = pendingTransition.displayId, + taskId = taskId, + immersive = pendingTransition.direction == Direction.ENTER, + ) + if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { + when (pendingTransition.direction) { + Direction.EXIT -> { + desktopRepository.removeBoundsBeforeFullImmersive(taskId) } - } - Direction.EXIT -> { - desktopRepository.setTaskInFullImmersiveState( - displayId = state.displayId, - taskId = state.taskId, - immersive = false, - ) - if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { - desktopRepository.removeBoundsBeforeFullImmersive(state.taskId) + Direction.ENTER -> { + desktopRepository.saveBoundsBeforeFullImmersive( + taskId, + immersiveChange.startAbsBounds, + ) } } } - return } // Check if this is an untracked exit transition, like display rotation. @@ -450,35 +427,31 @@ class DesktopImmersiveController( } override fun onTransitionMerged(merged: IBinder, playing: IBinder) { - val pendingExit = - pendingExternalExitTransitions.firstOrNull { pendingExit -> - pendingExit.transition == merged + val pendingTransition = + pendingImmersiveTransitions.firstOrNull { pendingTransition -> + pendingTransition.transition == merged } - if (pendingExit != null) { + if (pendingTransition != null) { logV( - "Pending exit transition %s for task#%s merged into %s", + "Pending transition %s for task#%s merged into %s", merged, - pendingExit.taskId, + pendingTransition.taskId, playing, ) - pendingExit.transition = playing + pendingTransition.transition = playing } } override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { - 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) + val pendingTransition = getImmersiveTransition(transition) + if (pendingTransition != null) { + logV("Pending exit transition %s for task#%s finished", transition, pendingTransition) + pendingImmersiveTransitions.remove(pendingTransition) } } - private fun clearState() { - state = null - } + private fun getImmersiveTransition(transition: IBinder) = + pendingImmersiveTransitions.firstOrNull { it.transition == transition } private fun getExitDestinationBounds(taskInfo: RunningTaskInfo): Rect { val displayLayout = @@ -496,24 +469,13 @@ class DesktopImmersiveController( } } - private fun requireState(): TransitionState = - state ?: error("Expected non-null transition state") - - private fun getRunningTransitions(): List<IBinder> { - val running = mutableListOf<IBinder>() - state?.let { running.add(it.transition) } - pendingExternalExitTransitions.forEach { running.add(it.transition) } - return running - } - - private fun TransitionInfo.hasTaskChange(taskId: Int): Boolean = - changes.any { c -> c.taskInfo?.taskId == taskId } + private fun TransitionInfo.getTaskChange(taskId: Int): TransitionInfo.Change? = + changes.firstOrNull { c -> c.taskInfo?.taskId == taskId } private fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("${prefix}DesktopImmersiveController") - pw.println(innerPrefix + "state=" + state) - pw.println(innerPrefix + "pendingExternalExitTransitions=" + pendingExternalExitTransitions) + pw.println(innerPrefix + "pendingImmersiveTransitions=" + pendingImmersiveTransitions) } /** The state of the currently running transition. */ @@ -526,12 +488,22 @@ class DesktopImmersiveController( ) /** - * Tracks state of a transition involving an immersive exit that is external to this class' own - * transitions. This usually means transitions that exit immersive mode as a side-effect and not - * the primary action (for example, minimizing the immersive task or launching a new task on top - * of the immersive task). + * Tracks state of a transition involving an immersive enter or exit. This includes both + * transitions that should and should not be animated by this handler. + * + * @param taskId of the task that should enter/exit immersive mode + * @param displayId of the display that should enter/exit immersive mode + * @param direction of the immersive transition + * @param transition that will apply this transaction + * @param animate whether transition should be animated by this handler */ - data class ExternalPendingExit(val taskId: Int, val displayId: Int, var transition: IBinder) + data class PendingTransition( + val taskId: Int, + val displayId: Int, + val direction: Direction, + var transition: IBinder, + val animate: Boolean, + ) /** The result of an external exit request. */ sealed class ExitResult { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt index db4c7465ae48..447da8799e35 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt @@ -42,6 +42,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.desktopmode.DesktopImmersiveController.Direction import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitReason.USER_INTERACTION import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.sysui.ShellInit @@ -95,6 +96,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() { whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { invocation -> (invocation.getArgument(0) as Rect).set(STABLE_BOUNDS) } + whenever(mockDisplayLayout.width()).thenReturn(DISPLAY_BOUNDS.width()) + whenever(mockDisplayLayout.height()).thenReturn(DISPLAY_BOUNDS.height()) controller = DesktopImmersiveController( shellInit = mock(), transitions = mockTransitions, @@ -277,10 +280,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) - assertThat(controller.pendingExternalExitTransitions.any { exit -> - exit.transition == transition && exit.displayId == DEFAULT_DISPLAY - && exit.taskId == task.taskId - }).isTrue() + assertTransitionPending( + transition = transition, + taskId = task.taskId, + direction = Direction.EXIT, + animate = false + ) } @Test @@ -298,10 +303,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) - assertThat(controller.pendingExternalExitTransitions.any { exit -> - exit.transition == transition && exit.displayId == DEFAULT_DISPLAY - && exit.taskId == task.taskId - }).isFalse() + assertTransitionNotPending( + transition = transition, + taskId = task.taskId, + direction = Direction.EXIT, + animate = false + ) } @Test @@ -360,10 +367,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() { reason = USER_INTERACTION, ).asExit()?.runOnTransitionStart?.invoke(transition) - assertThat(controller.pendingExternalExitTransitions.any { exit -> - exit.transition == transition && exit.displayId == DEFAULT_DISPLAY - && exit.taskId == task.taskId - }).isFalse() + assertTransitionNotPending( + transition = transition, + taskId = task.taskId, + animate = false, + direction = Direction.EXIT + ) } @Test @@ -416,10 +425,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) .asExit()?.runOnTransitionStart?.invoke(transition) - assertThat(controller.pendingExternalExitTransitions.any { exit -> - exit.transition == transition && exit.displayId == DEFAULT_DISPLAY - && exit.taskId == task.taskId - }).isTrue() + assertTransitionPending( + transition = transition, + taskId = task.taskId, + direction = Direction.EXIT, + animate = false + ) } @Test @@ -481,10 +492,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() { ) controller.onTransitionFinished(transition, aborted = false) - assertThat(controller.pendingExternalExitTransitions.any { exit -> - exit.transition == transition && exit.displayId == DEFAULT_DISPLAY - && exit.taskId == task.taskId - }).isFalse() + assertTransitionNotPending( + transition = transition, + taskId = task.taskId, + direction = Direction.EXIT, + animate = false + ) } @Test @@ -513,14 +526,18 @@ class DesktopImmersiveControllerTest : ShellTestCase() { 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() + assertTransitionNotPending( + transition = transition, + taskId = task.taskId, + animate = false, + direction = Direction.EXIT + ) + assertTransitionNotPending( + transition = mergedToTransition, + taskId = task.taskId, + animate = false, + direction = Direction.EXIT + ) } @Test @@ -686,7 +703,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun externalAnimateResizeChange_doesNotCleanUpPendingTransitionState() { + fun externalAnimateResizeChange_doesNotRemovePendingTransition() { val task = createFreeformTask() val mockBinder = mock(IBinder::class.java) whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) @@ -709,12 +726,16 @@ class DesktopImmersiveControllerTest : ShellTestCase() { ) animatorTestRule.advanceTimeBy(DesktopImmersiveController.FULL_IMMERSIVE_ANIM_DURATION_MS) - assertThat(controller.state).isNotNull() + assertTransitionPending( + transition = mockBinder, + taskId = task.taskId, + direction = Direction.EXIT + ) } @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun startAnimation_missingChange_clearsState() { + fun startAnimation_missingChange_removesPendingTransition() { val task = createFreeformTask() val mockBinder = mock(IBinder::class.java) whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) @@ -735,7 +756,42 @@ class DesktopImmersiveControllerTest : ShellTestCase() { finishCallback = {} ) - assertThat(controller.state).isNull() + assertTransitionNotPending( + transition = mockBinder, + taskId = task.taskId, + direction = Direction.ENTER + ) + } + + private fun assertTransitionPending( + transition: IBinder, + taskId: Int, + direction: Direction, + animate: Boolean = true, + displayId: Int = DEFAULT_DISPLAY + ) { + assertThat(controller.pendingImmersiveTransitions.any { pendingTransition -> + pendingTransition.transition == transition + && pendingTransition.displayId == displayId + && pendingTransition.taskId == taskId + && pendingTransition.animate == animate + && pendingTransition.direction == direction + }).isTrue() + } + + private fun assertTransitionNotPending( + transition: IBinder, + taskId: Int, + direction: Direction, + animate: Boolean = true, + displayId: Int = DEFAULT_DISPLAY + ) { + assertThat(controller.pendingImmersiveTransitions.any { pendingTransition -> + pendingTransition.transition == transition + && pendingTransition.displayId == displayId + && pendingTransition.taskId == taskId + && pendingTransition.direction == direction + }).isFalse() } private fun createTransitionInfo( @@ -768,5 +824,6 @@ class DesktopImmersiveControllerTest : ShellTestCase() { companion object { private val STABLE_BOUNDS = Rect(0, 100, 2000, 1900) + private val DISPLAY_BOUNDS = Rect(0, 0, 2000, 2000) } } |