Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt +2 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,8 @@ class DesktopDisplayEventHandler( // desk has been recreated here, which may result in a crash-loop if the repository is // checking that the desk exists before adding a task to it. See b/391984373. desktopTasksController.createDesk(displayId) // TODO: b/393978539 - consider activating the desk on creation when applicable, such as // for connected displays. } override fun onDisplayRemoved(displayId: Int) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +0 −1 Original line number Diff line number Diff line Loading @@ -238,7 +238,6 @@ class DesktopRepository( } /** Returns the id of the active desk in the given display, if any. */ @VisibleForTesting fun getActiveDeskId(displayId: Int): Int? = desktopData.getActiveDesk(displayId)?.deskId /** Returns the id of the desk to which this task belongs. */ Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +72 −21 Original line number Diff line number Diff line Loading @@ -1251,36 +1251,77 @@ class DesktopTasksController( return } val wct = WindowContainerTransaction() val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId) if (displayAreaInfo == null) { logW("moveToDisplay: display not found") return } val wct = WindowContainerTransaction() // check if the task is part of splitscreen if ( Flags.enableNonDefaultDisplaySplit() && Flags.enableMoveToNextDisplayShortcut() && splitScreenController.isTaskInSplitScreen(task.taskId) ) { val activeDeskId = taskRepository.getActiveDeskId(displayId) logV("moveToDisplay: moving split root to displayId=%d", displayId) val stageCoordinatorRootTaskToken = splitScreenController.multiDisplayProvider.getDisplayRootForDisplayId( DEFAULT_DISPLAY ) wct.reparent(stageCoordinatorRootTaskToken, displayAreaInfo.token, true /* onTop */) transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) val deactivationRunnable = if (activeDeskId != null) { // Split is being placed on top of an existing desk in the target display. Make // sure it is cleaned up. performDesktopExitCleanUp( wct = wct, deskId = activeDeskId, displayId = displayId, willExitDesktop = true, shouldEndUpAtHome = false, ) } else { null } val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) deactivationRunnable?.invoke(transition) return } val destinationDeskId = taskRepository.getDefaultDeskId(displayId) if (destinationDeskId == null) { logW("moveToDisplay: desk not found for display: $displayId") return } // TODO: b/393977830 and b/397437641 - do not assume that freeform==desktop. if (!task.isFreeform) { addMoveToDesktopChanges(wct, task, displayId) } else if (Flags.enableMoveToNextDisplayShortcut()) { applyFreeformDisplayChange(wct, task, displayId) } val activationRunnable: RunOnTransitStart? if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task) prepareForDeskActivation(displayId, wct) desksOrganizer.activateDesk(wct, destinationDeskId) activationRunnable = { transition -> desksTransitionObserver.addPendingTransition( DeskTransition.ActiveDeskWithTask( token = transition, displayId = displayId, deskId = destinationDeskId, enterTaskId = task.taskId, ) ) } } else { wct.reparent(task.token, displayAreaInfo.token, /* onTop= */ true) activationRunnable = null } if (Flags.enableDisplayFocusInShellTransitions()) { // Bring the destination display to top with includingParents=true, so that the // destination display gains the display focus, which makes the top task in the display Loading @@ -1288,22 +1329,31 @@ class DesktopTasksController( wct.reorder(task.token, /* onTop= */ true, /* includingParents= */ true) } // TODO: b/394268248 - desk needs to be deactivated when moving the last task and going // home. if (Flags.enablePerDisplayDesktopWallpaperActivity()) { val sourceDisplayId = task.displayId val sourceDeskId = taskRepository.getDeskIdForTask(task.taskId) val shouldExitDesktopIfNeeded = Flags.enablePerDisplayDesktopWallpaperActivity() || DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue val deactivationRunnable = if (shouldExitDesktopIfNeeded) { performDesktopExitCleanupIfNeeded( taskId = task.taskId, displayId = task.displayId, deskId = sourceDeskId, displayId = sourceDisplayId, wct = wct, forceToFullscreen = false, // TODO: b/371096166 - Temporary turing home relaunch off to prevent home stealing // TODO: b/371096166 - Temporary turing home relaunch off to prevent home // stealing // display focus. Remove shouldEndUpAtHome = false when home focus handling // with connected display is implemented in wm core. shouldEndUpAtHome = false, ) } else { null } transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) deactivationRunnable?.invoke(transition) activationRunnable?.invoke(transition) } /** Loading Loading @@ -2484,6 +2534,7 @@ class DesktopTasksController( val displayLayout = displayController.getDisplayLayout(displayId) ?: return val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)!! val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode // TODO: b/397437641 - reconsider the windowing mode choice when multiple desks is enabled. val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) { // Display windowing is freeform, set to undefined and inherit it Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt +20 −9 Original line number Diff line number Diff line Loading @@ -31,12 +31,14 @@ class DesksTransitionObserver( private val desktopUserRepositories: DesktopUserRepositories, private val desksOrganizer: DesksOrganizer, ) { private val deskTransitions = mutableMapOf<IBinder, DeskTransition>() private val deskTransitions = mutableMapOf<IBinder, MutableSet<DeskTransition>>() /** Adds a pending desk transition to be tracked. */ fun addPendingTransition(transition: DeskTransition) { if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return deskTransitions[transition.token] = transition val transitions = deskTransitions[transition.token] ?: mutableSetOf() transitions += transition deskTransitions[transition.token] = transitions } /** Loading @@ -45,7 +47,11 @@ class DesksTransitionObserver( */ fun onTransitionReady(transition: IBinder, info: TransitionInfo) { if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return val deskTransition = deskTransitions.remove(transition) ?: return val deskTransitions = deskTransitions.remove(transition) ?: return deskTransitions.forEach { deskTransition -> handleDeskTransition(info, deskTransition) } } private fun handleDeskTransition(info: TransitionInfo, deskTransition: DeskTransition) { logD("Desk transition ready: %s", deskTransition) val desktopRepository = desktopUserRepositories.current when (deskTransition) { Loading @@ -60,17 +66,22 @@ class DesksTransitionObserver( deskTransition.onDeskRemovedListener?.onDeskRemoved(displayId, deskId) } is DeskTransition.ActivateDesk -> { val activeDeskChange = val activateDeskChange = info.changes.find { change -> desksOrganizer.isDeskActiveAtEnd(change, deskTransition.deskId) } activeDeskChange?.let { if (activateDeskChange == null) { // Always activate even if there is no change in the transition for the // activated desk. This is necessary because some activation requests, such as // those involving empty desks, may not contain visibility changes that are // reported in the transition change list. logD("Activating desk without transition change") } desktopRepository.setActiveDesk( displayId = deskTransition.displayId, deskId = deskTransition.deskId, ) } } is DeskTransition.ActiveDeskWithTask -> { val withTask = info.changes.find { change -> Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +73 −0 Original line number Diff line number Diff line Loading @@ -2779,6 +2779,79 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() assertThat(taskChange.includingParents()).isTrue() } @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun moveToNextDisplay_toDeskInOtherDisplay_movesToDeskAndActivates() { val transition = Binder() val targetDeskId = 4 taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId) taskRepository.setDeskInactive(deskId = targetDeskId) // Set up two display ids whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) // Create a mock for the target display area: second display val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) .thenReturn(secondDisplayArea) whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull())) .thenReturn(transition) val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) taskRepository.addTaskToDesk( displayId = DEFAULT_DISPLAY, deskId = 0, taskId = task.taskId, isVisible = true, ) controller.moveToNextDisplay(task.taskId) verify(desksOrganizer).moveTaskToDesk(any(), eq(targetDeskId), eq(task)) verify(desksOrganizer).activateDesk(any(), eq(targetDeskId)) verify(desksTransitionsObserver) .addPendingTransition( DeskTransition.ActiveDeskWithTask( token = transition, displayId = SECOND_DISPLAY, deskId = targetDeskId, enterTaskId = task.taskId, ) ) } @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun moveToNextDisplay_wasLastTaskInSourceDesk_deactivates() { val transition = Binder() val sourceDeskId = 0 val targetDeskId = 4 taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId) taskRepository.setDeskInactive(deskId = targetDeskId) // Set up two display ids whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) // Create a mock for the target display area: second display val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) .thenReturn(secondDisplayArea) whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull())) .thenReturn(transition) val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) taskRepository.addTaskToDesk( displayId = DEFAULT_DISPLAY, deskId = sourceDeskId, taskId = task.taskId, isVisible = true, ) controller.moveToNextDisplay(task.taskId) verify(desksOrganizer).deactivateDesk(any(), eq(sourceDeskId)) verify(desksTransitionsObserver) .addPendingTransition( DeskTransition.DeactivateDesk(token = transition, deskId = sourceDeskId) ) } @Test fun getTaskWindowingMode() { val fullscreenTask = setUpFullscreenTask() Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt +2 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,8 @@ class DesktopDisplayEventHandler( // desk has been recreated here, which may result in a crash-loop if the repository is // checking that the desk exists before adding a task to it. See b/391984373. desktopTasksController.createDesk(displayId) // TODO: b/393978539 - consider activating the desk on creation when applicable, such as // for connected displays. } override fun onDisplayRemoved(displayId: Int) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +0 −1 Original line number Diff line number Diff line Loading @@ -238,7 +238,6 @@ class DesktopRepository( } /** Returns the id of the active desk in the given display, if any. */ @VisibleForTesting fun getActiveDeskId(displayId: Int): Int? = desktopData.getActiveDesk(displayId)?.deskId /** Returns the id of the desk to which this task belongs. */ Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +72 −21 Original line number Diff line number Diff line Loading @@ -1251,36 +1251,77 @@ class DesktopTasksController( return } val wct = WindowContainerTransaction() val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId) if (displayAreaInfo == null) { logW("moveToDisplay: display not found") return } val wct = WindowContainerTransaction() // check if the task is part of splitscreen if ( Flags.enableNonDefaultDisplaySplit() && Flags.enableMoveToNextDisplayShortcut() && splitScreenController.isTaskInSplitScreen(task.taskId) ) { val activeDeskId = taskRepository.getActiveDeskId(displayId) logV("moveToDisplay: moving split root to displayId=%d", displayId) val stageCoordinatorRootTaskToken = splitScreenController.multiDisplayProvider.getDisplayRootForDisplayId( DEFAULT_DISPLAY ) wct.reparent(stageCoordinatorRootTaskToken, displayAreaInfo.token, true /* onTop */) transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) val deactivationRunnable = if (activeDeskId != null) { // Split is being placed on top of an existing desk in the target display. Make // sure it is cleaned up. performDesktopExitCleanUp( wct = wct, deskId = activeDeskId, displayId = displayId, willExitDesktop = true, shouldEndUpAtHome = false, ) } else { null } val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) deactivationRunnable?.invoke(transition) return } val destinationDeskId = taskRepository.getDefaultDeskId(displayId) if (destinationDeskId == null) { logW("moveToDisplay: desk not found for display: $displayId") return } // TODO: b/393977830 and b/397437641 - do not assume that freeform==desktop. if (!task.isFreeform) { addMoveToDesktopChanges(wct, task, displayId) } else if (Flags.enableMoveToNextDisplayShortcut()) { applyFreeformDisplayChange(wct, task, displayId) } val activationRunnable: RunOnTransitStart? if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task) prepareForDeskActivation(displayId, wct) desksOrganizer.activateDesk(wct, destinationDeskId) activationRunnable = { transition -> desksTransitionObserver.addPendingTransition( DeskTransition.ActiveDeskWithTask( token = transition, displayId = displayId, deskId = destinationDeskId, enterTaskId = task.taskId, ) ) } } else { wct.reparent(task.token, displayAreaInfo.token, /* onTop= */ true) activationRunnable = null } if (Flags.enableDisplayFocusInShellTransitions()) { // Bring the destination display to top with includingParents=true, so that the // destination display gains the display focus, which makes the top task in the display Loading @@ -1288,22 +1329,31 @@ class DesktopTasksController( wct.reorder(task.token, /* onTop= */ true, /* includingParents= */ true) } // TODO: b/394268248 - desk needs to be deactivated when moving the last task and going // home. if (Flags.enablePerDisplayDesktopWallpaperActivity()) { val sourceDisplayId = task.displayId val sourceDeskId = taskRepository.getDeskIdForTask(task.taskId) val shouldExitDesktopIfNeeded = Flags.enablePerDisplayDesktopWallpaperActivity() || DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue val deactivationRunnable = if (shouldExitDesktopIfNeeded) { performDesktopExitCleanupIfNeeded( taskId = task.taskId, displayId = task.displayId, deskId = sourceDeskId, displayId = sourceDisplayId, wct = wct, forceToFullscreen = false, // TODO: b/371096166 - Temporary turing home relaunch off to prevent home stealing // TODO: b/371096166 - Temporary turing home relaunch off to prevent home // stealing // display focus. Remove shouldEndUpAtHome = false when home focus handling // with connected display is implemented in wm core. shouldEndUpAtHome = false, ) } else { null } transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) deactivationRunnable?.invoke(transition) activationRunnable?.invoke(transition) } /** Loading Loading @@ -2484,6 +2534,7 @@ class DesktopTasksController( val displayLayout = displayController.getDisplayLayout(displayId) ?: return val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)!! val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode // TODO: b/397437641 - reconsider the windowing mode choice when multiple desks is enabled. val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) { // Display windowing is freeform, set to undefined and inherit it Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt +20 −9 Original line number Diff line number Diff line Loading @@ -31,12 +31,14 @@ class DesksTransitionObserver( private val desktopUserRepositories: DesktopUserRepositories, private val desksOrganizer: DesksOrganizer, ) { private val deskTransitions = mutableMapOf<IBinder, DeskTransition>() private val deskTransitions = mutableMapOf<IBinder, MutableSet<DeskTransition>>() /** Adds a pending desk transition to be tracked. */ fun addPendingTransition(transition: DeskTransition) { if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return deskTransitions[transition.token] = transition val transitions = deskTransitions[transition.token] ?: mutableSetOf() transitions += transition deskTransitions[transition.token] = transitions } /** Loading @@ -45,7 +47,11 @@ class DesksTransitionObserver( */ fun onTransitionReady(transition: IBinder, info: TransitionInfo) { if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return val deskTransition = deskTransitions.remove(transition) ?: return val deskTransitions = deskTransitions.remove(transition) ?: return deskTransitions.forEach { deskTransition -> handleDeskTransition(info, deskTransition) } } private fun handleDeskTransition(info: TransitionInfo, deskTransition: DeskTransition) { logD("Desk transition ready: %s", deskTransition) val desktopRepository = desktopUserRepositories.current when (deskTransition) { Loading @@ -60,17 +66,22 @@ class DesksTransitionObserver( deskTransition.onDeskRemovedListener?.onDeskRemoved(displayId, deskId) } is DeskTransition.ActivateDesk -> { val activeDeskChange = val activateDeskChange = info.changes.find { change -> desksOrganizer.isDeskActiveAtEnd(change, deskTransition.deskId) } activeDeskChange?.let { if (activateDeskChange == null) { // Always activate even if there is no change in the transition for the // activated desk. This is necessary because some activation requests, such as // those involving empty desks, may not contain visibility changes that are // reported in the transition change list. logD("Activating desk without transition change") } desktopRepository.setActiveDesk( displayId = deskTransition.displayId, deskId = deskTransition.deskId, ) } } is DeskTransition.ActiveDeskWithTask -> { val withTask = info.changes.find { change -> Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +73 −0 Original line number Diff line number Diff line Loading @@ -2779,6 +2779,79 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() assertThat(taskChange.includingParents()).isTrue() } @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun moveToNextDisplay_toDeskInOtherDisplay_movesToDeskAndActivates() { val transition = Binder() val targetDeskId = 4 taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId) taskRepository.setDeskInactive(deskId = targetDeskId) // Set up two display ids whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) // Create a mock for the target display area: second display val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) .thenReturn(secondDisplayArea) whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull())) .thenReturn(transition) val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) taskRepository.addTaskToDesk( displayId = DEFAULT_DISPLAY, deskId = 0, taskId = task.taskId, isVisible = true, ) controller.moveToNextDisplay(task.taskId) verify(desksOrganizer).moveTaskToDesk(any(), eq(targetDeskId), eq(task)) verify(desksOrganizer).activateDesk(any(), eq(targetDeskId)) verify(desksTransitionsObserver) .addPendingTransition( DeskTransition.ActiveDeskWithTask( token = transition, displayId = SECOND_DISPLAY, deskId = targetDeskId, enterTaskId = task.taskId, ) ) } @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun moveToNextDisplay_wasLastTaskInSourceDesk_deactivates() { val transition = Binder() val sourceDeskId = 0 val targetDeskId = 4 taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId) taskRepository.setDeskInactive(deskId = targetDeskId) // Set up two display ids whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) // Create a mock for the target display area: second display val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) .thenReturn(secondDisplayArea) whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull())) .thenReturn(transition) val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) taskRepository.addTaskToDesk( displayId = DEFAULT_DISPLAY, deskId = sourceDeskId, taskId = task.taskId, isVisible = true, ) controller.moveToNextDisplay(task.taskId) verify(desksOrganizer).deactivateDesk(any(), eq(sourceDeskId)) verify(desksTransitionsObserver) .addPendingTransition( DeskTransition.DeactivateDesk(token = transition, deskId = sourceDeskId) ) } @Test fun getTaskWindowingMode() { val fullscreenTask = setUpFullscreenTask() Loading