diff options
author | 2025-02-06 15:11:00 +0000 | |
---|---|---|
committer | 2025-02-14 01:32:35 +0000 | |
commit | c6e124f46ab9b3a8958c12a0364cac61dc556aed (patch) | |
tree | 6ba55adfa319f5638651f11512667be42299be82 | |
parent | 5dc10c1f0982048dadd5490c63c9cebf8c925870 (diff) |
[15/N] Desks: Deactivate desk when last window is minimized
When the last task is minimized, the desk is now deactivated to prevent
future app launches from launching in freeform.
A small refactor is made to move the deactivation and transition
tracking to a utility method.
Note: minimization is not yet migrated to multi-desks, so while the desk
is now deactivated, the minimized task remains visible because it is
only being sent to the back of its desk container, which effectively
keeps it in place since there's no home/wallpaper inside the desk.
Flag: com.android.window.flags.enable_multiple_desktops_backend
Bug: 391485148
Bug: 394268248
Test: open fullscreen app, enter desktop, minimize it
1) DesktopRepository dump shows activeDeskId=null
2) Home launches
Change-Id: I25eb436595732cf6831f2a64f067cd771e337153
4 files changed, 145 insertions, 47 deletions
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 1395d335bcd9..0afceac8a861 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 @@ -153,6 +153,16 @@ import java.util.concurrent.TimeUnit import java.util.function.Consumer import kotlin.jvm.optionals.getOrNull +/** + * A callback to be invoked when a transition is started via |Transitions.startTransition| with the + * transition binder token that it produces. + * + * Useful when multiple components are appending WCT operations to a single transition that is + * started outside of their control, and each of them wants to track the transition lifecycle + * independently by cross-referencing the transition token with future ready-transitions. + */ +typealias RunOnTransitStart = (IBinder) -> Unit + /** Handles moving tasks in and out of desktop */ class DesktopTasksController( private val context: Context, @@ -792,6 +802,7 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, ): ((IBinder) -> Unit) { val taskId = taskInfo.taskId + val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId) snapEventHandler.removeTaskIfTiled(displayId, taskId) val shouldExitDesktop = willExitDesktop( @@ -800,28 +811,14 @@ class DesktopTasksController( forceToFullscreen = false, ) taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true) - // TODO: b/393978539 - Deactivation should not happen in desktop-first devices. - val deactivatingDeskId = - if (shouldExitDesktop) { - performDesktopExitCleanUp(wct, displayId, shouldEndUpAtHome = true) - val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId) - if ( - DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue && deskId != null - ) { - desksOrganizer.deactivateDesk(wct, deskId) - } - deskId - } else { - null - } - val deskDeactivationRunnable = - deactivatingDeskId?.let { deskId -> - { transition: IBinder -> - desksTransitionObserver.addPendingTransition( - DeskTransition.DeactivateDesk(token = transition, deskId = deskId) - ) - } - } + val desktopExitRunnable = + performDesktopExitCleanUp( + wct = wct, + deskId = deskId, + displayId = displayId, + willExitDesktop = shouldExitDesktop, + shouldEndUpAtHome = true, + ) taskRepository.addClosingTask(displayId, taskId) taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( @@ -839,7 +836,7 @@ class DesktopTasksController( ?.runOnTransitionStart return { transitionToken -> immersiveRunnable?.invoke(transitionToken) - deskDeactivationRunnable?.invoke(transitionToken) + desktopExitRunnable?.invoke(transitionToken) } } @@ -874,12 +871,20 @@ class DesktopTasksController( private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) { val taskId = taskInfo.taskId + val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId) val displayId = taskInfo.displayId val wct = WindowContainerTransaction() + snapEventHandler.removeTaskIfTiled(displayId, taskId) - // TODO: b/394268248 - desk needs to be deactivated when minimizing the last task and going - // home. - performDesktopExitCleanupIfNeeded(taskId, displayId, wct, forceToFullscreen = false) + taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true) + val willExitDesktop = willExitDesktop(taskId, displayId, forceToFullscreen = false) + val desktopExitRunnable = + performDesktopExitCleanUp( + wct = wct, + deskId = deskId, + displayId = displayId, + willExitDesktop = willExitDesktop, + ) // Notify immersive handler as it might need to exit immersive state. val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( @@ -901,6 +906,7 @@ class DesktopTasksController( ) } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) + desktopExitRunnable?.invoke(transition) } /** Move a task with given `taskId` to fullscreen */ @@ -949,7 +955,7 @@ class DesktopTasksController( logV("moveToFullscreenWithAnimation taskId=%d", task.taskId) val wct = WindowContainerTransaction() val willExitDesktop = willExitDesktop(task.taskId, task.displayId, forceToFullscreen = true) - val deactivatingDeskId = addMoveToFullscreenChanges(wct, task, willExitDesktop) + val deactivationRunnable = addMoveToFullscreenChanges(wct, task, willExitDesktop) // We are moving a freeform task to fullscreen, put the home task under the fullscreen task. if (!forceEnterDesktop(task.displayId)) { @@ -964,11 +970,7 @@ class DesktopTasksController( position, mOnAnimationFinishedCallback, ) - if (deactivatingDeskId != null) { - desksTransitionObserver.addPendingTransition( - DeskTransition.DeactivateDesk(token = transition, deskId = deactivatingDeskId) - ) - } + deactivationRunnable?.invoke(transition) // handles case where we are moving to full screen without closing all DW tasks. if (!taskRepository.isOnlyVisibleNonClosingTask(task.taskId)) { @@ -1804,19 +1806,30 @@ class DesktopTasksController( wct: WindowContainerTransaction, forceToFullscreen: Boolean, shouldEndUpAtHome: Boolean = true, - ) { + ): RunOnTransitStart? { taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = !forceToFullscreen) if (!willExitDesktop(taskId, displayId, forceToFullscreen)) { - return + return null } - performDesktopExitCleanUp(wct, displayId, shouldEndUpAtHome) + // TODO: b/394268248 - update remaining callers to pass in a |deskId| and apply the + // |RunOnTransitStart| when the transition is started. + return performDesktopExitCleanUp( + wct = wct, + deskId = null, + displayId = displayId, + willExitDesktop = true, + shouldEndUpAtHome = shouldEndUpAtHome, + ) } private fun performDesktopExitCleanUp( wct: WindowContainerTransaction, + deskId: Int?, displayId: Int, + willExitDesktop: Boolean, shouldEndUpAtHome: Boolean = true, - ) { + ): RunOnTransitStart? { + if (!willExitDesktop) return null desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted( FULLSCREEN_ANIMATION_DURATION ) @@ -1826,6 +1839,7 @@ class DesktopTasksController( // intent. addLaunchHomePendingIntent(wct, displayId) } + return prepareDeskDeactivationIfNeeded(wct, deskId) } fun releaseVisualIndicator() { @@ -2507,7 +2521,7 @@ class DesktopTasksController( wct: WindowContainerTransaction, taskInfo: RunningTaskInfo, willExitDesktop: Boolean, - ): Int? { + ): RunOnTransitStart? { val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!! val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode val targetWindowingMode = @@ -2526,15 +2540,14 @@ class DesktopTasksController( wct.reparent(taskInfo.token, tdaInfo.token, /* onTop= */ true) } taskRepository.setPipShouldKeepDesktopActive(taskInfo.displayId, keepActive = false) - if (willExitDesktop) { - performDesktopExitCleanUp(wct, taskInfo.displayId, shouldEndUpAtHome = false) - val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId) - if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue && deskId != null) { - desksOrganizer.deactivateDesk(wct, deskId) - return deskId - } - } - return null + val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId) + return performDesktopExitCleanUp( + wct = wct, + deskId = deskId, + displayId = taskInfo.displayId, + willExitDesktop = willExitDesktop, + shouldEndUpAtHome = false, + ) } private fun cascadeWindow(bounds: Rect, displayLayout: DisplayLayout, displayId: Int) { @@ -2695,6 +2708,23 @@ class DesktopTasksController( ) } + /** + * TODO: b/393978539 - Deactivation should not happen in desktop-first devices when going home. + */ + private fun prepareDeskDeactivationIfNeeded( + wct: WindowContainerTransaction, + deskId: Int?, + ): RunOnTransitStart? { + if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return null + if (deskId == null) return null + desksOrganizer.deactivateDesk(wct, deskId) + return { transition -> + desksTransitionObserver.addPendingTransition( + DeskTransition.DeactivateDesk(token = transition, deskId = deskId) + ) + } + } + /** Removes the default desk in the given display. */ @Deprecated("Deprecated with multi-desks.", ReplaceWith("removeDesk()")) fun removeDefaultDeskInDisplay(displayId: Int) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt index d4586abc8ec4..e57b56378fb3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt @@ -92,10 +92,11 @@ class DesksTransitionObserver( } } is DeskTransition.DeactivateDesk -> { + var visibleDeactivation = false for (change in info.changes) { val isDeskChange = desksOrganizer.isDeskChange(change, deskTransition.deskId) if (isDeskChange) { - desktopRepository.setDeskInactive(deskId = deskTransition.deskId) + visibleDeactivation = true continue } val taskId = change.taskInfo?.taskId ?: continue @@ -109,6 +110,14 @@ class DesksTransitionObserver( ) } } + // Always deactivate even if there's no change that confirms the desk was + // deactivated. Some interactions, such as the desk deactivating because it's + // occluded by a fullscreen task result in a transition change, but others, such + // as transitioning from an empty desk to home may not. + if (!visibleDeactivation) { + logD("Deactivating desk without transition change") + } + desktopRepository.setDeskInactive(deskId = deskTransition.deskId) } } } 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 a88d576ad1c3..e2c3dda0d927 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 @@ -2971,6 +2971,48 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun onDesktopWindowMinimize_lastWindow_deactivatesDesk() { + val task = setUpFreeformTask() + val transition = Binder() + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) + .thenReturn(transition) + + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) + + val captor = argumentCaptor<WindowContainerTransaction>() + verify(freeformTaskTransitionStarter) + .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true)) + verify(desksOrganizer).deactivateDesk(captor.firstValue, deskId = 0) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun onDesktopWindowMinimize_lastWindow_addsPendingDeactivateTransition() { + val task = setUpFreeformTask() + val transition = Binder() + whenever( + freeformTaskTransitionStarter.startMinimizedModeTransition( + any(), + anyInt(), + anyBoolean(), + ) + ) + .thenReturn(transition) + + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) + + verify(desksTransitionsObserver) + .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0)) + } + + @Test fun onPipTaskMinimize_autoEnterEnabled_startPipTransition() { val task = setUpPipTask(autoEnterEnabled = true) val handler = mock(TransitionHandler::class.java) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt index 79310c9ce6c2..4dcf669f4d25 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt @@ -227,4 +227,21 @@ class DesksTransitionObserverTest : ShellTestCase() { assertThat(repository.isActiveTaskInDesk(deskId = 5, taskId = exitingTask.taskId)).isFalse() } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun onTransitionReady_deactivateDeskWithoutVisibleChange_updatesRepository() { + val transition = Binder() + val deactivateTransition = DeskTransition.DeactivateDesk(transition, deskId = 5) + repository.addDesk(DEFAULT_DISPLAY, deskId = 5) + repository.setActiveDesk(DEFAULT_DISPLAY, deskId = 5) + + observer.addPendingTransition(deactivateTransition) + observer.onTransitionReady( + transition = transition, + info = TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0), + ) + + assertThat(repository.getActiveDeskId(DEFAULT_DISPLAY)).isNull() + } } |