Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt +25 −11 Original line number Diff line number Diff line Loading @@ -188,12 +188,28 @@ class DesktopDisplayEventHandler( private fun handlePotentialReconnect(displayId: Int): Boolean { val uniqueDisplayId = displayController.getDisplay(displayId)?.uniqueId ?: return false uniqueIdByDisplayId[displayId] = uniqueDisplayId val currentUserRepository = desktopUserRepositories.current if ( DesktopExperienceFlags.ENABLE_DISPLAY_RECONNECT_INTERACTION.isTrue && desktopUserRepositories.current.hasPreservedDisplayForUniqueDisplayId( uniqueDisplayId ) !DesktopExperienceFlags.ENABLE_DISPLAY_RECONNECT_INTERACTION.isTrue || !currentUserRepository.hasPreservedDisplayForUniqueDisplayId(uniqueDisplayId) ) { return false } val preservedTasks = currentUserRepository.getPreservedTasks(uniqueDisplayId) // Projected mode: Do not move anything focused on the internal display. if (!desktopState.isDesktopModeSupportedOnDisplay(DEFAULT_DISPLAY)) { val focusedDefaultDisplayTaskIds = desktopTasksController .getFocusedNonDesktopTasks(DEFAULT_DISPLAY, currentUserRepository.userId) .map { task -> task.taskId } preservedTasks.filterNot { taskId -> focusedDefaultDisplayTaskIds.contains(taskId) } } if (preservedTasks.isEmpty()) { // The preserved display is normally removed at the end of restoreDisplay. // If we don't restore anything, remove it here instead. currentUserRepository.removePreservedDisplay(uniqueDisplayId) return false } desktopTasksController.restoreDisplay( displayId = displayId, uniqueDisplayId = uniqueDisplayId, Loading @@ -201,8 +217,6 @@ class DesktopDisplayEventHandler( ) return true } return false } override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) { if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +151 −116 Original line number Diff line number Diff line Loading @@ -44,7 +44,6 @@ import android.os.Binder import android.os.Bundle import android.os.Handler import android.os.IBinder import android.os.SystemProperties import android.os.Trace import android.os.UserHandle import android.os.UserManager Loading Loading @@ -127,6 +126,7 @@ import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.desktopmode.data.DesktopRepository import com.android.wm.shell.desktopmode.data.DesktopRepository.Companion.INVALID_DESK_ID import com.android.wm.shell.desktopmode.data.DesktopRepository.DeskChangeListener import com.android.wm.shell.desktopmode.data.DesktopRepository.VisibleTasksListener Loading Loading @@ -750,7 +750,7 @@ class DesktopTasksController( "onDisplayDisconnect: disconnectedDisplayId=$disconnectedDisplayId, " + "destinationDisplayId=$destinationDisplayId" ) val runOnTransitStartSet = mutableListOf<RunOnTransitStart>() val runOnTransitStartList = mutableListOf<RunOnTransitStart>() preserveDisplayRequestHandler?.requestPreserveDisplay(disconnectedDisplayId) // TODO: b/406320371 - Verify this works with non-system users once the underlying bug is // resolved. Loading @@ -772,18 +772,54 @@ class DesktopTasksController( val userId = desktopRepository.userId val deskIds = desktopRepository.getDeskIds(disconnectedDisplayId).toList() if (desktopModeSupportedOnDisplay) { handleExtendedModeDisconnect( desktopRepository, wct, runOnTransitStartList, deskIds, disconnectedDisplayId, destinationDisplayId, destDisplayLayout, userId, ) } else { handleProjectedModeDisconnect( desktopRepository, wct, runOnTransitStartList, deskIds, disconnectedDisplayId, destinationDisplayId, destDisplayLayout, userId, ) } } return { transition -> for (runOnTransitStart in runOnTransitStartList) { runOnTransitStart(transition) } } } private fun handleExtendedModeDisconnect( desktopRepository: DesktopRepository, wct: WindowContainerTransaction, runOnTransitStartList: MutableList<RunOnTransitStart>, deskIds: List<Int>, disconnectedDisplayId: Int, destinationDisplayId: Int, destDisplayLayout: DisplayLayout?, userId: Int, ) { // Desktop supported on display; reparent desks, focused desk on top. for (deskId in deskIds) { val deskTasks = desktopRepository.getActiveTaskIdsInDesk(deskId) // Remove desk if it's empty. if (deskTasks.isEmpty()) { logD( "onDisplayDisconnect: removing empty desk=%d of user=%d", deskId, userId, ) logD("onDisplayDisconnect: removing empty desk=%d of user=%d", deskId, userId) desksOrganizer.removeDesk(wct, deskId, userId) runOnTransitStartSet.add { transition -> runOnTransitStartList.add { transition -> desksTransitionObserver.addPendingTransition( DeskTransition.RemoveDesk( token = transition, Loading @@ -805,18 +841,15 @@ class DesktopTasksController( userId, ) // Otherwise, reparent it to the destination display. val toTop = deskTasks.contains(focusTransitionObserver.globallyFocusedTaskId) val toTop = deskTasks.contains(focusTransitionObserver.globallyFocusedTaskId) desksOrganizer.moveDeskToDisplay(wct, deskId, destinationDisplayId, toTop) val taskIds = desktopRepository.getActiveTaskIdsInDesk(deskId) for (taskId in taskIds) { val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: continue destDisplayLayout?.densityDpi()?.let { wct.setDensityDpi(task.token, it) } destDisplayLayout?.densityDpi()?.let { wct.setDensityDpi(task.token, it) } applyFreeformDisplayChange(wct, task, destinationDisplayId, deskId) } runOnTransitStartSet.add { transition -> runOnTransitStartList.add { transition -> desksTransitionObserver.addPendingTransition( DeskTransition.ChangeDeskDisplay( token = transition, Loading @@ -833,31 +866,36 @@ class DesktopTasksController( wct = wct, toTop = toTop, ) ?.let { runOnTransitStartSet.add(it) } ?.let { runOnTransitStartList.add(it) } } } } else { logD("onDisplayDisconnect: moving tasks to non-desktop display") } private fun handleProjectedModeDisconnect( desktopRepository: DesktopRepository, wct: WindowContainerTransaction, runOnTransitStartList: MutableList<RunOnTransitStart>, deskIds: List<Int>, disconnectedDisplayId: Int, destinationDisplayId: Int, destDisplayLayout: DisplayLayout?, userId: Int, ) { logD("handleProjectedModeDisconnect: moving tasks to non-desktop display") // Desktop not supported on display; reparent tasks to display area, remove desk. val tdaInfo = checkNotNull( rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(destinationDisplayId) ) { checkNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(destinationDisplayId)) { "Expected to find displayAreaInfo for displayId=$destinationDisplayId" } for (deskId in deskIds) { val taskIds = desktopRepository.getActiveTaskIdsInDesk(deskId) for (taskId in taskIds) { val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: continue wct.reparent( task.token, tdaInfo.token, focusTransitionObserver.globallyFocusedTaskId == task.taskId, ) wct.reparent(task.token, tdaInfo.token, /* onTop= */ false) destDisplayLayout?.densityDpi()?.let { wct.setDensityDpi(task.token, it) } } desksOrganizer.removeDesk(wct, deskId, userId) runOnTransitStartSet.add { transition -> runOnTransitStartList.add { transition -> desksTransitionObserver.addPendingTransition( DeskTransition.RemoveDesk( token = transition, Loading @@ -880,13 +918,6 @@ class DesktopTasksController( } } } } return { transition -> for (runOnTransitStart in runOnTransitStartSet) { runOnTransitStart(transition) } } } /** * Restore a display based on info that was stored on disconnect. Loading @@ -911,6 +942,8 @@ class DesktopTasksController( val destDisplayLayout = displayController.getDisplayLayout(displayId) ?: return val tilingReconnectHandler = TilingDisplayReconnectEventHandler(repository, snapEventHandler, transitions, displayId) val excludedTasks = getFocusedNonDesktopTasks(DEFAULT_DISPLAY, userId).map { task -> task.taskId } mainScope.launch { preservedTaskIdsByDeskId.forEach { (preservedDeskId, preservedTaskIds) -> val newDeskId = Loading @@ -935,6 +968,7 @@ class DesktopTasksController( } preservedTaskIds.asReversed().forEach { taskId -> if (!excludedTasks.contains(taskId)) { addRestoreTaskToDeskChanges( wct = wct, destinationDisplayLayout = destDisplayLayout, Loading @@ -945,6 +979,7 @@ class DesktopTasksController( taskBounds = boundsByTaskId[taskId], ) } } val preservedTilingData = repository.getPreservedTilingData(uniqueDisplayId, preservedDeskId) Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/DesktopRepository.kt +13 −0 Original line number Diff line number Diff line Loading @@ -154,6 +154,19 @@ class DesktopRepository( return tasksByDeskId } /** * Returns all preserved tasks for the preserved display regardless of what desk they appear in. */ fun getPreservedTasks(uniqueDisplayId: String): List<Int> { val preservedDesks = preservedDisplaysByUniqueId[uniqueDisplayId]?.orderedDesks ?: emptySet() val preservedTasks = mutableListOf<Int>() for (desk in preservedDesks) { desk.freeformTasksInZOrder.forEach { taskId -> preservedTasks.add(taskId) } } return preservedTasks } /** Returns the active desk on the preserved display for the specified unique display id. */ fun getPreservedActiveDesk(uniqueDisplayId: String): Int? = preservedDisplaysByUniqueId[uniqueDisplayId]?.activeDeskId Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt +28 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.view.Display Loading Loading @@ -448,11 +449,38 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { desktopState.overrideDesktopModeSupportPerDisplay[externalDisplayId] = true whenever(mockDesktopRepository.hasPreservedDisplayForUniqueDisplayId(UNIQUE_DISPLAY_ID)) .thenReturn(true) val preservedFocusedTaskIds = listOf(1) whenever(mockDesktopRepository.getPreservedTasks(UNIQUE_DISPLAY_ID)) .thenReturn(preservedFocusedTaskIds) whenever(mockDesktopTasksController.getFocusedNonDesktopTasks(any(), any())) .thenReturn(emptyList()) onDisplaysChangedListenerCaptor.lastValue.onDesktopModeEligibleChanged(externalDisplayId) verify(mockDesktopTasksController) .restoreDisplay(eq(externalDisplayId), eq(UNIQUE_DISPLAY_ID), eq(PRIMARY_USER_ID)) } @Test @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION) fun testDisplayReconnected_tasksFocusedOnDefaultDisplay_skipsReconnect() { desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = false desktopState.overrideDesktopModeSupportPerDisplay[externalDisplayId] = true whenever(mockDesktopRepository.hasPreservedDisplayForUniqueDisplayId(UNIQUE_DISPLAY_ID)) .thenReturn(true) val preservedFocusedTaskIds = listOf(1) whenever(mockDesktopRepository.getPreservedTasks(UNIQUE_DISPLAY_ID)) .thenReturn(preservedFocusedTaskIds) val task = RunningTaskInfo().apply { this.taskId = 1 } whenever(mockDesktopTasksController.getFocusedNonDesktopTasks(any(), any())) .thenReturn(listOf(task)) addDisplay(2) verify(mockDesktopTasksController, never()) .restoreDisplay(eq(externalDisplayId), eq(UNIQUE_DISPLAY_ID), eq(PRIMARY_USER_ID)) } private fun addDisplay(displayId: Int, withTda: Boolean = false) { onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(displayId) if (withTda) { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +1 −50 Original line number Diff line number Diff line Loading @@ -10743,13 +10743,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION, Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, ) fun onDisplayDisconnect_desktopModeNotSupported_reparentsDeskTasks_focusedTaskToTop() { fun onDisplayDisconnect_desktopModeNotSupported_reparentsDeskTasks_focusedTaskToBottom() { val defaultDisplayTask = setUpFullscreenTask() val transition = Binder() taskRepository.addDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID) val secondDisplayTask = setUpFreeformTask(SECOND_DISPLAY) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! val wct = performDisplayDisconnectTransition( transition = transition, Loading @@ -10758,54 +10757,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() defaultDisplayTask = defaultDisplayTask, secondDisplayTask = secondDisplayTask, ) assertThat(wct).isNotNull() wct.assertHop( ReparentPredicate( token = secondDisplayTask.token, parentToken = tda.token, toTop = true, ) ) verify(desksOrganizer).removeDesk(any(), eq(DISCONNECTED_DESK_ID), any()) verify(desksTransitionsObserver) .addPendingTransition( argThat { this is DeskTransition.RemoveDesk && this.token == transition && this.displayId == SECOND_DISPLAY && this.deskId == DISCONNECTED_DESK_ID } ) verify(desksTransitionsObserver) .addPendingTransition( argThat { this is DeskTransition.RemoveDisplay && this.token == transition && this.displayId == SECOND_DISPLAY } ) } @Test @EnableFlags( Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION, Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, ) fun onDisplayDisconnect_desktopModeNotSupported_reparentsDeskTasks_nonFocusedTaskToBottom() { val defaultDisplayTask = setUpFullscreenTask() val transition = Binder() taskRepository.addDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID) val secondDisplayTask = setUpFreeformTask(SECOND_DISPLAY) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! val wct = performDisplayDisconnectTransition( transition = transition, desktopSupportedOnDefaultDisplay = false, taskOnSecondDisplayHasFocus = false, defaultDisplayTask = defaultDisplayTask, secondDisplayTask = secondDisplayTask, ) wct.assertHop( ReparentPredicate( token = secondDisplayTask.token, Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt +25 −11 Original line number Diff line number Diff line Loading @@ -188,12 +188,28 @@ class DesktopDisplayEventHandler( private fun handlePotentialReconnect(displayId: Int): Boolean { val uniqueDisplayId = displayController.getDisplay(displayId)?.uniqueId ?: return false uniqueIdByDisplayId[displayId] = uniqueDisplayId val currentUserRepository = desktopUserRepositories.current if ( DesktopExperienceFlags.ENABLE_DISPLAY_RECONNECT_INTERACTION.isTrue && desktopUserRepositories.current.hasPreservedDisplayForUniqueDisplayId( uniqueDisplayId ) !DesktopExperienceFlags.ENABLE_DISPLAY_RECONNECT_INTERACTION.isTrue || !currentUserRepository.hasPreservedDisplayForUniqueDisplayId(uniqueDisplayId) ) { return false } val preservedTasks = currentUserRepository.getPreservedTasks(uniqueDisplayId) // Projected mode: Do not move anything focused on the internal display. if (!desktopState.isDesktopModeSupportedOnDisplay(DEFAULT_DISPLAY)) { val focusedDefaultDisplayTaskIds = desktopTasksController .getFocusedNonDesktopTasks(DEFAULT_DISPLAY, currentUserRepository.userId) .map { task -> task.taskId } preservedTasks.filterNot { taskId -> focusedDefaultDisplayTaskIds.contains(taskId) } } if (preservedTasks.isEmpty()) { // The preserved display is normally removed at the end of restoreDisplay. // If we don't restore anything, remove it here instead. currentUserRepository.removePreservedDisplay(uniqueDisplayId) return false } desktopTasksController.restoreDisplay( displayId = displayId, uniqueDisplayId = uniqueDisplayId, Loading @@ -201,8 +217,6 @@ class DesktopDisplayEventHandler( ) return true } return false } override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) { if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +151 −116 Original line number Diff line number Diff line Loading @@ -44,7 +44,6 @@ import android.os.Binder import android.os.Bundle import android.os.Handler import android.os.IBinder import android.os.SystemProperties import android.os.Trace import android.os.UserHandle import android.os.UserManager Loading Loading @@ -127,6 +126,7 @@ import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.desktopmode.data.DesktopRepository import com.android.wm.shell.desktopmode.data.DesktopRepository.Companion.INVALID_DESK_ID import com.android.wm.shell.desktopmode.data.DesktopRepository.DeskChangeListener import com.android.wm.shell.desktopmode.data.DesktopRepository.VisibleTasksListener Loading Loading @@ -750,7 +750,7 @@ class DesktopTasksController( "onDisplayDisconnect: disconnectedDisplayId=$disconnectedDisplayId, " + "destinationDisplayId=$destinationDisplayId" ) val runOnTransitStartSet = mutableListOf<RunOnTransitStart>() val runOnTransitStartList = mutableListOf<RunOnTransitStart>() preserveDisplayRequestHandler?.requestPreserveDisplay(disconnectedDisplayId) // TODO: b/406320371 - Verify this works with non-system users once the underlying bug is // resolved. Loading @@ -772,18 +772,54 @@ class DesktopTasksController( val userId = desktopRepository.userId val deskIds = desktopRepository.getDeskIds(disconnectedDisplayId).toList() if (desktopModeSupportedOnDisplay) { handleExtendedModeDisconnect( desktopRepository, wct, runOnTransitStartList, deskIds, disconnectedDisplayId, destinationDisplayId, destDisplayLayout, userId, ) } else { handleProjectedModeDisconnect( desktopRepository, wct, runOnTransitStartList, deskIds, disconnectedDisplayId, destinationDisplayId, destDisplayLayout, userId, ) } } return { transition -> for (runOnTransitStart in runOnTransitStartList) { runOnTransitStart(transition) } } } private fun handleExtendedModeDisconnect( desktopRepository: DesktopRepository, wct: WindowContainerTransaction, runOnTransitStartList: MutableList<RunOnTransitStart>, deskIds: List<Int>, disconnectedDisplayId: Int, destinationDisplayId: Int, destDisplayLayout: DisplayLayout?, userId: Int, ) { // Desktop supported on display; reparent desks, focused desk on top. for (deskId in deskIds) { val deskTasks = desktopRepository.getActiveTaskIdsInDesk(deskId) // Remove desk if it's empty. if (deskTasks.isEmpty()) { logD( "onDisplayDisconnect: removing empty desk=%d of user=%d", deskId, userId, ) logD("onDisplayDisconnect: removing empty desk=%d of user=%d", deskId, userId) desksOrganizer.removeDesk(wct, deskId, userId) runOnTransitStartSet.add { transition -> runOnTransitStartList.add { transition -> desksTransitionObserver.addPendingTransition( DeskTransition.RemoveDesk( token = transition, Loading @@ -805,18 +841,15 @@ class DesktopTasksController( userId, ) // Otherwise, reparent it to the destination display. val toTop = deskTasks.contains(focusTransitionObserver.globallyFocusedTaskId) val toTop = deskTasks.contains(focusTransitionObserver.globallyFocusedTaskId) desksOrganizer.moveDeskToDisplay(wct, deskId, destinationDisplayId, toTop) val taskIds = desktopRepository.getActiveTaskIdsInDesk(deskId) for (taskId in taskIds) { val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: continue destDisplayLayout?.densityDpi()?.let { wct.setDensityDpi(task.token, it) } destDisplayLayout?.densityDpi()?.let { wct.setDensityDpi(task.token, it) } applyFreeformDisplayChange(wct, task, destinationDisplayId, deskId) } runOnTransitStartSet.add { transition -> runOnTransitStartList.add { transition -> desksTransitionObserver.addPendingTransition( DeskTransition.ChangeDeskDisplay( token = transition, Loading @@ -833,31 +866,36 @@ class DesktopTasksController( wct = wct, toTop = toTop, ) ?.let { runOnTransitStartSet.add(it) } ?.let { runOnTransitStartList.add(it) } } } } else { logD("onDisplayDisconnect: moving tasks to non-desktop display") } private fun handleProjectedModeDisconnect( desktopRepository: DesktopRepository, wct: WindowContainerTransaction, runOnTransitStartList: MutableList<RunOnTransitStart>, deskIds: List<Int>, disconnectedDisplayId: Int, destinationDisplayId: Int, destDisplayLayout: DisplayLayout?, userId: Int, ) { logD("handleProjectedModeDisconnect: moving tasks to non-desktop display") // Desktop not supported on display; reparent tasks to display area, remove desk. val tdaInfo = checkNotNull( rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(destinationDisplayId) ) { checkNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(destinationDisplayId)) { "Expected to find displayAreaInfo for displayId=$destinationDisplayId" } for (deskId in deskIds) { val taskIds = desktopRepository.getActiveTaskIdsInDesk(deskId) for (taskId in taskIds) { val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: continue wct.reparent( task.token, tdaInfo.token, focusTransitionObserver.globallyFocusedTaskId == task.taskId, ) wct.reparent(task.token, tdaInfo.token, /* onTop= */ false) destDisplayLayout?.densityDpi()?.let { wct.setDensityDpi(task.token, it) } } desksOrganizer.removeDesk(wct, deskId, userId) runOnTransitStartSet.add { transition -> runOnTransitStartList.add { transition -> desksTransitionObserver.addPendingTransition( DeskTransition.RemoveDesk( token = transition, Loading @@ -880,13 +918,6 @@ class DesktopTasksController( } } } } return { transition -> for (runOnTransitStart in runOnTransitStartSet) { runOnTransitStart(transition) } } } /** * Restore a display based on info that was stored on disconnect. Loading @@ -911,6 +942,8 @@ class DesktopTasksController( val destDisplayLayout = displayController.getDisplayLayout(displayId) ?: return val tilingReconnectHandler = TilingDisplayReconnectEventHandler(repository, snapEventHandler, transitions, displayId) val excludedTasks = getFocusedNonDesktopTasks(DEFAULT_DISPLAY, userId).map { task -> task.taskId } mainScope.launch { preservedTaskIdsByDeskId.forEach { (preservedDeskId, preservedTaskIds) -> val newDeskId = Loading @@ -935,6 +968,7 @@ class DesktopTasksController( } preservedTaskIds.asReversed().forEach { taskId -> if (!excludedTasks.contains(taskId)) { addRestoreTaskToDeskChanges( wct = wct, destinationDisplayLayout = destDisplayLayout, Loading @@ -945,6 +979,7 @@ class DesktopTasksController( taskBounds = boundsByTaskId[taskId], ) } } val preservedTilingData = repository.getPreservedTilingData(uniqueDisplayId, preservedDeskId) Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/DesktopRepository.kt +13 −0 Original line number Diff line number Diff line Loading @@ -154,6 +154,19 @@ class DesktopRepository( return tasksByDeskId } /** * Returns all preserved tasks for the preserved display regardless of what desk they appear in. */ fun getPreservedTasks(uniqueDisplayId: String): List<Int> { val preservedDesks = preservedDisplaysByUniqueId[uniqueDisplayId]?.orderedDesks ?: emptySet() val preservedTasks = mutableListOf<Int>() for (desk in preservedDesks) { desk.freeformTasksInZOrder.forEach { taskId -> preservedTasks.add(taskId) } } return preservedTasks } /** Returns the active desk on the preserved display for the specified unique display id. */ fun getPreservedActiveDesk(uniqueDisplayId: String): Int? = preservedDisplaysByUniqueId[uniqueDisplayId]?.activeDeskId Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt +28 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.view.Display Loading Loading @@ -448,11 +449,38 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { desktopState.overrideDesktopModeSupportPerDisplay[externalDisplayId] = true whenever(mockDesktopRepository.hasPreservedDisplayForUniqueDisplayId(UNIQUE_DISPLAY_ID)) .thenReturn(true) val preservedFocusedTaskIds = listOf(1) whenever(mockDesktopRepository.getPreservedTasks(UNIQUE_DISPLAY_ID)) .thenReturn(preservedFocusedTaskIds) whenever(mockDesktopTasksController.getFocusedNonDesktopTasks(any(), any())) .thenReturn(emptyList()) onDisplaysChangedListenerCaptor.lastValue.onDesktopModeEligibleChanged(externalDisplayId) verify(mockDesktopTasksController) .restoreDisplay(eq(externalDisplayId), eq(UNIQUE_DISPLAY_ID), eq(PRIMARY_USER_ID)) } @Test @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION) fun testDisplayReconnected_tasksFocusedOnDefaultDisplay_skipsReconnect() { desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = false desktopState.overrideDesktopModeSupportPerDisplay[externalDisplayId] = true whenever(mockDesktopRepository.hasPreservedDisplayForUniqueDisplayId(UNIQUE_DISPLAY_ID)) .thenReturn(true) val preservedFocusedTaskIds = listOf(1) whenever(mockDesktopRepository.getPreservedTasks(UNIQUE_DISPLAY_ID)) .thenReturn(preservedFocusedTaskIds) val task = RunningTaskInfo().apply { this.taskId = 1 } whenever(mockDesktopTasksController.getFocusedNonDesktopTasks(any(), any())) .thenReturn(listOf(task)) addDisplay(2) verify(mockDesktopTasksController, never()) .restoreDisplay(eq(externalDisplayId), eq(UNIQUE_DISPLAY_ID), eq(PRIMARY_USER_ID)) } private fun addDisplay(displayId: Int, withTda: Boolean = false) { onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(displayId) if (withTda) { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +1 −50 Original line number Diff line number Diff line Loading @@ -10743,13 +10743,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION, Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, ) fun onDisplayDisconnect_desktopModeNotSupported_reparentsDeskTasks_focusedTaskToTop() { fun onDisplayDisconnect_desktopModeNotSupported_reparentsDeskTasks_focusedTaskToBottom() { val defaultDisplayTask = setUpFullscreenTask() val transition = Binder() taskRepository.addDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID) val secondDisplayTask = setUpFreeformTask(SECOND_DISPLAY) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! val wct = performDisplayDisconnectTransition( transition = transition, Loading @@ -10758,54 +10757,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() defaultDisplayTask = defaultDisplayTask, secondDisplayTask = secondDisplayTask, ) assertThat(wct).isNotNull() wct.assertHop( ReparentPredicate( token = secondDisplayTask.token, parentToken = tda.token, toTop = true, ) ) verify(desksOrganizer).removeDesk(any(), eq(DISCONNECTED_DESK_ID), any()) verify(desksTransitionsObserver) .addPendingTransition( argThat { this is DeskTransition.RemoveDesk && this.token == transition && this.displayId == SECOND_DISPLAY && this.deskId == DISCONNECTED_DESK_ID } ) verify(desksTransitionsObserver) .addPendingTransition( argThat { this is DeskTransition.RemoveDisplay && this.token == transition && this.displayId == SECOND_DISPLAY } ) } @Test @EnableFlags( Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION, Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, ) fun onDisplayDisconnect_desktopModeNotSupported_reparentsDeskTasks_nonFocusedTaskToBottom() { val defaultDisplayTask = setUpFullscreenTask() val transition = Binder() taskRepository.addDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID) val secondDisplayTask = setUpFreeformTask(SECOND_DISPLAY) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! val wct = performDisplayDisconnectTransition( transition = transition, desktopSupportedOnDefaultDisplay = false, taskOnSecondDisplayHasFocus = false, defaultDisplayTask = defaultDisplayTask, secondDisplayTask = secondDisplayTask, ) wct.assertHop( ReparentPredicate( token = secondDisplayTask.token, Loading