Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +6 −1 Original line number Diff line number Diff line Loading @@ -1412,18 +1412,23 @@ public abstract class WMShellModule { desktopState, shellInit))); } @WMSingleton @Provides static Optional<DesksTransitionObserver> provideDesksTransitionObserver( @DynamicOverride DesktopUserRepositories desktopUserRepositories, @NonNull DesksOrganizer desksOrganizer, @NonNull Transitions transitions, @NonNull ShellController shellController, @NonNull DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, @NonNull @ShellMainThread CoroutineScope mainScope, DesktopState desktopState ) { if (desktopState.canEnterDesktopModeOrShowAppHandle()) { return Optional.of( new DesksTransitionObserver(desktopUserRepositories, desksOrganizer, transitions)); transitions, shellController, desktopWallpaperActivityTokenProvider, mainScope)); } return Optional.empty(); } Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt +269 −20 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ package com.android.wm.shell.desktopmode.multidesks import android.app.ActivityTaskManager.INVALID_TASK_ID import android.os.IBinder import android.view.Display.INVALID_DISPLAY import android.view.WindowManager.TRANSIT_CHANGE Loading @@ -23,11 +24,21 @@ import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.DesktopExperienceFlags import android.window.TransitionInfo import android.window.TransitionInfo.FLAG_MOVED_TO_TOP import android.window.WindowContainerTransaction import com.android.app.tracing.traceSection import com.android.internal.protolog.ProtoLog import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_END_RECENTS_TRANSITION import com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch /** * Observer of desk-related transitions, such as adding, removing or activating a whole desk. It Loading @@ -37,6 +48,9 @@ class DesksTransitionObserver( private val desktopUserRepositories: DesktopUserRepositories, private val desksOrganizer: DesksOrganizer, private val transitions: Transitions, private val shellController: ShellController, private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, @ShellMainThread private val mainScope: CoroutineScope, ) { private val deskTransitions = mutableMapOf<IBinder, MutableSet<DeskTransition>>() Loading @@ -53,13 +67,16 @@ class DesksTransitionObserver( * Called when any transition is ready, which may include transitions not tracked by this * observer. */ fun onTransitionReady(transition: IBinder, info: TransitionInfo) { fun onTransitionReady(transition: IBinder, info: TransitionInfo) = traceSection("DesksTransitionObserver#onTransitionReady") { if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return val deskTransitions = deskTransitions.remove(transition) deskTransitions?.forEach { deskTransition -> handleDeskTransition(info, deskTransition) } if (deskTransitions == null || deskTransitions.isEmpty()) { // A desk transition could also occur without shell having started it or intercepting // it, check for that here in case launch roots need to be updated. deskTransitions?.forEach { deskTransition -> handleDeskTransition(info, deskTransition) } if (deskTransitions.isNullOrEmpty()) { // A desk transition could also occur without shell having started it or // intercepting it, check for that here in case launch roots need to be updated. handleIndependentDeskTransitionIfNeeded(info) } } Loading Loading @@ -231,23 +248,193 @@ class DesksTransitionObserver( } private fun handleIndependentDeskTransitionIfNeeded(info: TransitionInfo) { val deskChanges = info.changes.filter { c -> desksOrganizer.isDeskChange(c) } if (deskChanges.isEmpty()) return val deskChanges = info.deskChanges() val desktopWallpaperChanges = info.desktopWallpaperChanges() if (deskChanges.isEmpty() && desktopWallpaperChanges.isEmpty()) return logD( "handleIndependentDeskTransitionIfNeeded %d desk related changes found", "handleIndependentDeskTransitionIfNeeded %d desk related change(s) found with " + "%d desktop wallpaper change(s)", deskChanges.size, desktopWallpaperChanges.size, ) if (info.isRecentsType()) { // Recents related changes are transient, so desk activation state should not change. logD("Desk changes in a recents transition, ignoring") return } val wct = WindowContainerTransaction() for (change in deskChanges.reversed()) { val deskId = desksOrganizer.getDeskIdFromChange(change) ?: continue when (change.mode) { TRANSIT_TO_BACK -> { logD("handleIndependentDeskTransitionIfNeeded desk=%d moved to back", deskId) var hasSeenDesk = false val openingUserIds = mutableListOf<Int>() val desksToActivate = mutableListOf<Int>() // Visit all task changes, not just desk/wallpaper changes because we're interested in // capturing user ids of showing tasks (such as Home) to detect user switches. for (change in info.taskChanges()) { val taskInfo = checkNotNull(change.taskInfo) { "Expected non-null task info" } val taskId = taskInfo.taskId logD("Handle change for taskId=%d:", taskId) if (TransitionUtil.isOpeningMode(change.mode) || change.isToTop()) { logD("Opening/to-top change for userId=%d", taskInfo.userId) openingUserIds += taskInfo.userId } if (change !in deskChanges && change !in desktopWallpaperChanges) { logD("Not desk or wallpaper, skipping") continue } if (change in deskChanges) { hasSeenDesk = true } val changeUserId = taskInfo.userId val userSwitch = getUserSwitch(change, openingUserIds) logD( "Independent change userSwitch=%s changeUserId=%d openingUserIds=%s", userSwitch, changeUserId, openingUserIds, ) if (change in desktopWallpaperChanges) { logD("Desktop wallpaper change") if (hasSeenDesk) { logD("Saw desk change before desktop wallpaper change, skipping") continue } when { change.isToBack() -> { // The desktop wallpaper is moving to back without seeing a desk first. // This might mean an empty desk is moving to back, such as when having an // empty desk active and switching users. Make sure the desk is deactivated. // If this is a user switch, the userId of the desk change will have // updated to the new user already, so use the old user id for reporting the // deactivation to the going-away user's repository. val userId = userSwitch?.oldUserId ?: changeUserId val repository = desktopUserRepositories.getProfile(userId) // When moving to back due to a user switch, keep the repository state as // active for when the user session is restored. val keepActiveInRepository = userSwitch != null val activeDeskId = repository.getActiveDeskId(change.endDisplayId) if (activeDeskId != null) { logD( "Desktop wallpaper of user=%d moved to back without visible desk" + ", will let the organizer deactivate " + "and the repository will %s", userId, if (keepActiveInRepository) "keep active" else "deactivate", ) // Always let the organizer deactivate to clear the launch root. desksOrganizer.deactivateDesk(wct, activeDeskId, skipReorder = true) if (!keepActiveInRepository) { // The desk was independently deactivated (such as when Home is // brought to front during CTS), make sure the repository state // reflects that too. repository.setDeskInactive(activeDeskId) } } else { logD( "Desktop wallpaper of user=%d moved to back with no active desk" + ", skipping", userId, ) } } change.isToTop() -> { // It is possible that the desktop wallpaper ends up on top of the desk. // For example: when in an empty desktop, then switching users and back. // WM restoration on user switches is only for visible root tasks, and an // empty desk root task is technically invisible (even if top / "active"), // so only the wallpaper is restored (in front of the desk). // This isn't valid desktop state, so if a desk was supposed to be active // activate it, otherwise dismiss the wallpaper. val repository = desktopUserRepositories.getProfile(changeUserId) val activeDeskId = repository.getActiveDeskId(change.endDisplayId) logD( "Found desktop wallpaper without a desk in front for userId=%d " + "activeDeskId=%d", changeUserId, activeDeskId, ) if (activeDeskId != null) { logD("Reactivating desk=%d", activeDeskId) desksToActivate.add(activeDeskId) desksOrganizer.activateDesk(wct, activeDeskId, skipReorder = false) } else { logD("Dismissing desktop wallpaper") val container = checkNotNull(change.container) { "Expected non-null container" } wct.reorder(container, /* onTop= */ false) } } else -> { logW( "Unexpected change for desktop wallpaper with mode=%s", TransitionInfo.modeToString(change.mode), ) } } continue } val deskId = desksOrganizer.getDeskIdFromChange(change) if (deskId == null) { logW("No desk found in change") continue } val displayId = change.endDisplayId val usersWithDeskId = desktopUserRepositories.getRepositoriesWithDeskId(deskId).map { it.userId } logD( "Handle desk change for desk=%d in display=%d usersWithDeskId=%s", deskId, displayId, usersWithDeskId, ) when { change.isToBack() -> { if ( desksToActivate.contains(deskId) && DesktopExperienceFlags.ENABLE_EMPTY_DESK_ON_MINIMIZE.isTrue ) { // In cases such as back-navigating the last app, where we want to remain // in an empty desk, WM core may forcefully move the desktop wallpaper to // front and the desk root to back because the next focusable activity is // the desktop wallpaper. This is another case of desktop wallpaper over // desk that's already handled above so the desk is already scheduled to be // reactivated. Just skip the deactivation here to avoid cancelling it out. logD("desk=%d moved to back but is scheduled to activate, skipping", deskId) continue } // If this is a user switch, the userId of the desk change will have updated to // the new user already, so it can't be used for reporting the deactivation // to the going-away user's repository. val userId = userSwitch?.oldUserId ?: changeUserId val repository = desktopUserRepositories.getProfile(userId) // When moving to back due to a user switch, keep the repository state as // active for when the user session is restored. val keepActiveInRepository = userSwitch != null logD( "desk=%d of user=%d moved to back, will let the organizer deactivate and " + "the repository will %s", deskId, userId, if (keepActiveInRepository) "keep active" else "deactivate", ) // Always let the organizer deactivate to clear the launch root. desksOrganizer.deactivateDesk(wct, deskId, skipReorder = true) if (!keepActiveInRepository) { // The desk was independently deactivated (such as when Home is brought // to front during CTS), make sure the repository state reflects that too. repository.setDeskInactive(deskId) } } TRANSIT_TO_FRONT -> { logD("handleIndependentDeskTransitionIfNeeded desk=%d moved to front", deskId) change.isToTop() -> { val repository = desktopUserRepositories.getProfile(changeUserId) logD( "desk=%d of user=%d moved to front, " + "will let the organizer and repository activate", deskId, changeUserId, ) desksOrganizer.activateDesk(wct, deskId, skipReorder = true) repository.setActiveDesk(displayId, deskId) } else -> { logW( Loading @@ -259,9 +446,71 @@ class DesksTransitionObserver( } } if (wct.isEmpty) { logD("No changes, ignoring") return } transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) logD("handleIndependentDeskTransitionIfNeeded starting transition") mainScope.launch { transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) } } private fun getUserSwitch( change: TransitionInfo.Change, openingUserIds: List<Int>, ): UserSwitch? { val taskInfo = checkNotNull(change.taskInfo) { "Expected non-null task info" } val currentUserId = shellController.currentUserId val changeUserId = taskInfo.userId // This isn't a reliable way to identify user switches. It makes two assumptions: // 1) When switching from user A to user B with a desk active, the transition // change for the desk is reported with the id of user B - that is because root // tasks are not tied to a single user, so WM always sets the ID of the "current" // (post-switch) user. // 2) The transition is handled by Shell before Shell knows about the user switch // so |ShellController.userId| still reports user A. // TODO: b/418821815 - make desk ids unique even across users, or include user // change info in the transition. val isUserSwitch = currentUserId != changeUserId || // In some cases, like when switching users from one with an empty desk: // 1) there is no desk change because it was empty and thus already invisible // 2) the desktop wallpaper going to back has the userId of the new user (because // it uses |showForAllUsers|. // So the only way to identify the user switch is by looking at other tasks (of // the new user) that are opening/to-top, such as Home if say the new user is // is restoring to Home. openingUserIds.filterNot { it == currentUserId }.isNotEmpty() if (!isUserSwitch) return null val newUserId = if (currentUserId != changeUserId) { changeUserId } else { openingUserIds.filterNot { it == currentUserId }.first() } return UserSwitch(oldUserId = currentUserId, newUserId = newUserId) } data class UserSwitch(val oldUserId: Int, val newUserId: Int) private fun TransitionInfo.isRecentsType() = type == TRANSIT_START_RECENTS_TRANSITION || type == TRANSIT_END_RECENTS_TRANSITION private fun TransitionInfo.Change.isToTop(): Boolean = (mode == TRANSIT_TO_FRONT) || hasFlags(FLAG_MOVED_TO_TOP) private fun TransitionInfo.Change.isToBack(): Boolean = mode == TRANSIT_TO_BACK private fun TransitionInfo.taskChanges(): List<TransitionInfo.Change> = changes.filter { c -> val taskId = c.taskInfo?.taskId taskId != null && taskId != INVALID_TASK_ID } private fun TransitionInfo.deskChanges(): List<TransitionInfo.Change> = changes.filter { c -> desksOrganizer.isDeskChange(c) } private fun TransitionInfo.desktopWallpaperChanges(): List<TransitionInfo.Change> = changes.filter { c -> c.container == desktopWallpaperActivityTokenProvider.getToken(c.endDisplayId) } private fun logD(msg: String, vararg arguments: Any?) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt +12 −2 Original line number Diff line number Diff line Loading @@ -514,7 +514,12 @@ class RootTaskDesksOrganizer( if (deskRootsByDeskId.contains(taskInfo.taskId)) { val deskId = taskInfo.taskId deskRootsByDeskId[deskId] = deskRootsByDeskId[deskId].copy(taskInfo = taskInfo) logV("Desk #$deskId's task info changed in display#${taskInfo.displayId}") logV( "Desk #$deskId's task info changed in display#%d visible=%b children=%s", taskInfo.displayId, taskInfo.isVisible, deskRootsByDeskId[deskId].children, ) return } val minimizationRoot = Loading @@ -531,7 +536,12 @@ class RootTaskDesksOrganizer( if (parentTaskId in deskRootsByDeskId) { val deskId = taskInfo.parentTaskId val taskId = taskInfo.taskId logV("onTaskInfoChanged: Task #$taskId appeared in desk #$deskId") logV( "onTaskInfoChanged: Task #%d (visible=%b) appeared in desk #%d", taskId, taskInfo.isVisible, deskId, ) addChildToDesk(taskId = taskId, deskId = deskId) return } Loading libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +1 −2 Original line number Diff line number Diff line Loading @@ -279,8 +279,7 @@ public class ShellController { } /** Returns the current user id. */ @VisibleForTesting int getCurrentUserId() { public int getCurrentUserId() { return mUserId; } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt +460 −37 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +6 −1 Original line number Diff line number Diff line Loading @@ -1412,18 +1412,23 @@ public abstract class WMShellModule { desktopState, shellInit))); } @WMSingleton @Provides static Optional<DesksTransitionObserver> provideDesksTransitionObserver( @DynamicOverride DesktopUserRepositories desktopUserRepositories, @NonNull DesksOrganizer desksOrganizer, @NonNull Transitions transitions, @NonNull ShellController shellController, @NonNull DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, @NonNull @ShellMainThread CoroutineScope mainScope, DesktopState desktopState ) { if (desktopState.canEnterDesktopModeOrShowAppHandle()) { return Optional.of( new DesksTransitionObserver(desktopUserRepositories, desksOrganizer, transitions)); transitions, shellController, desktopWallpaperActivityTokenProvider, mainScope)); } return Optional.empty(); } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt +269 −20 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ package com.android.wm.shell.desktopmode.multidesks import android.app.ActivityTaskManager.INVALID_TASK_ID import android.os.IBinder import android.view.Display.INVALID_DISPLAY import android.view.WindowManager.TRANSIT_CHANGE Loading @@ -23,11 +24,21 @@ import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.DesktopExperienceFlags import android.window.TransitionInfo import android.window.TransitionInfo.FLAG_MOVED_TO_TOP import android.window.WindowContainerTransaction import com.android.app.tracing.traceSection import com.android.internal.protolog.ProtoLog import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_END_RECENTS_TRANSITION import com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch /** * Observer of desk-related transitions, such as adding, removing or activating a whole desk. It Loading @@ -37,6 +48,9 @@ class DesksTransitionObserver( private val desktopUserRepositories: DesktopUserRepositories, private val desksOrganizer: DesksOrganizer, private val transitions: Transitions, private val shellController: ShellController, private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, @ShellMainThread private val mainScope: CoroutineScope, ) { private val deskTransitions = mutableMapOf<IBinder, MutableSet<DeskTransition>>() Loading @@ -53,13 +67,16 @@ class DesksTransitionObserver( * Called when any transition is ready, which may include transitions not tracked by this * observer. */ fun onTransitionReady(transition: IBinder, info: TransitionInfo) { fun onTransitionReady(transition: IBinder, info: TransitionInfo) = traceSection("DesksTransitionObserver#onTransitionReady") { if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return val deskTransitions = deskTransitions.remove(transition) deskTransitions?.forEach { deskTransition -> handleDeskTransition(info, deskTransition) } if (deskTransitions == null || deskTransitions.isEmpty()) { // A desk transition could also occur without shell having started it or intercepting // it, check for that here in case launch roots need to be updated. deskTransitions?.forEach { deskTransition -> handleDeskTransition(info, deskTransition) } if (deskTransitions.isNullOrEmpty()) { // A desk transition could also occur without shell having started it or // intercepting it, check for that here in case launch roots need to be updated. handleIndependentDeskTransitionIfNeeded(info) } } Loading Loading @@ -231,23 +248,193 @@ class DesksTransitionObserver( } private fun handleIndependentDeskTransitionIfNeeded(info: TransitionInfo) { val deskChanges = info.changes.filter { c -> desksOrganizer.isDeskChange(c) } if (deskChanges.isEmpty()) return val deskChanges = info.deskChanges() val desktopWallpaperChanges = info.desktopWallpaperChanges() if (deskChanges.isEmpty() && desktopWallpaperChanges.isEmpty()) return logD( "handleIndependentDeskTransitionIfNeeded %d desk related changes found", "handleIndependentDeskTransitionIfNeeded %d desk related change(s) found with " + "%d desktop wallpaper change(s)", deskChanges.size, desktopWallpaperChanges.size, ) if (info.isRecentsType()) { // Recents related changes are transient, so desk activation state should not change. logD("Desk changes in a recents transition, ignoring") return } val wct = WindowContainerTransaction() for (change in deskChanges.reversed()) { val deskId = desksOrganizer.getDeskIdFromChange(change) ?: continue when (change.mode) { TRANSIT_TO_BACK -> { logD("handleIndependentDeskTransitionIfNeeded desk=%d moved to back", deskId) var hasSeenDesk = false val openingUserIds = mutableListOf<Int>() val desksToActivate = mutableListOf<Int>() // Visit all task changes, not just desk/wallpaper changes because we're interested in // capturing user ids of showing tasks (such as Home) to detect user switches. for (change in info.taskChanges()) { val taskInfo = checkNotNull(change.taskInfo) { "Expected non-null task info" } val taskId = taskInfo.taskId logD("Handle change for taskId=%d:", taskId) if (TransitionUtil.isOpeningMode(change.mode) || change.isToTop()) { logD("Opening/to-top change for userId=%d", taskInfo.userId) openingUserIds += taskInfo.userId } if (change !in deskChanges && change !in desktopWallpaperChanges) { logD("Not desk or wallpaper, skipping") continue } if (change in deskChanges) { hasSeenDesk = true } val changeUserId = taskInfo.userId val userSwitch = getUserSwitch(change, openingUserIds) logD( "Independent change userSwitch=%s changeUserId=%d openingUserIds=%s", userSwitch, changeUserId, openingUserIds, ) if (change in desktopWallpaperChanges) { logD("Desktop wallpaper change") if (hasSeenDesk) { logD("Saw desk change before desktop wallpaper change, skipping") continue } when { change.isToBack() -> { // The desktop wallpaper is moving to back without seeing a desk first. // This might mean an empty desk is moving to back, such as when having an // empty desk active and switching users. Make sure the desk is deactivated. // If this is a user switch, the userId of the desk change will have // updated to the new user already, so use the old user id for reporting the // deactivation to the going-away user's repository. val userId = userSwitch?.oldUserId ?: changeUserId val repository = desktopUserRepositories.getProfile(userId) // When moving to back due to a user switch, keep the repository state as // active for when the user session is restored. val keepActiveInRepository = userSwitch != null val activeDeskId = repository.getActiveDeskId(change.endDisplayId) if (activeDeskId != null) { logD( "Desktop wallpaper of user=%d moved to back without visible desk" + ", will let the organizer deactivate " + "and the repository will %s", userId, if (keepActiveInRepository) "keep active" else "deactivate", ) // Always let the organizer deactivate to clear the launch root. desksOrganizer.deactivateDesk(wct, activeDeskId, skipReorder = true) if (!keepActiveInRepository) { // The desk was independently deactivated (such as when Home is // brought to front during CTS), make sure the repository state // reflects that too. repository.setDeskInactive(activeDeskId) } } else { logD( "Desktop wallpaper of user=%d moved to back with no active desk" + ", skipping", userId, ) } } change.isToTop() -> { // It is possible that the desktop wallpaper ends up on top of the desk. // For example: when in an empty desktop, then switching users and back. // WM restoration on user switches is only for visible root tasks, and an // empty desk root task is technically invisible (even if top / "active"), // so only the wallpaper is restored (in front of the desk). // This isn't valid desktop state, so if a desk was supposed to be active // activate it, otherwise dismiss the wallpaper. val repository = desktopUserRepositories.getProfile(changeUserId) val activeDeskId = repository.getActiveDeskId(change.endDisplayId) logD( "Found desktop wallpaper without a desk in front for userId=%d " + "activeDeskId=%d", changeUserId, activeDeskId, ) if (activeDeskId != null) { logD("Reactivating desk=%d", activeDeskId) desksToActivate.add(activeDeskId) desksOrganizer.activateDesk(wct, activeDeskId, skipReorder = false) } else { logD("Dismissing desktop wallpaper") val container = checkNotNull(change.container) { "Expected non-null container" } wct.reorder(container, /* onTop= */ false) } } else -> { logW( "Unexpected change for desktop wallpaper with mode=%s", TransitionInfo.modeToString(change.mode), ) } } continue } val deskId = desksOrganizer.getDeskIdFromChange(change) if (deskId == null) { logW("No desk found in change") continue } val displayId = change.endDisplayId val usersWithDeskId = desktopUserRepositories.getRepositoriesWithDeskId(deskId).map { it.userId } logD( "Handle desk change for desk=%d in display=%d usersWithDeskId=%s", deskId, displayId, usersWithDeskId, ) when { change.isToBack() -> { if ( desksToActivate.contains(deskId) && DesktopExperienceFlags.ENABLE_EMPTY_DESK_ON_MINIMIZE.isTrue ) { // In cases such as back-navigating the last app, where we want to remain // in an empty desk, WM core may forcefully move the desktop wallpaper to // front and the desk root to back because the next focusable activity is // the desktop wallpaper. This is another case of desktop wallpaper over // desk that's already handled above so the desk is already scheduled to be // reactivated. Just skip the deactivation here to avoid cancelling it out. logD("desk=%d moved to back but is scheduled to activate, skipping", deskId) continue } // If this is a user switch, the userId of the desk change will have updated to // the new user already, so it can't be used for reporting the deactivation // to the going-away user's repository. val userId = userSwitch?.oldUserId ?: changeUserId val repository = desktopUserRepositories.getProfile(userId) // When moving to back due to a user switch, keep the repository state as // active for when the user session is restored. val keepActiveInRepository = userSwitch != null logD( "desk=%d of user=%d moved to back, will let the organizer deactivate and " + "the repository will %s", deskId, userId, if (keepActiveInRepository) "keep active" else "deactivate", ) // Always let the organizer deactivate to clear the launch root. desksOrganizer.deactivateDesk(wct, deskId, skipReorder = true) if (!keepActiveInRepository) { // The desk was independently deactivated (such as when Home is brought // to front during CTS), make sure the repository state reflects that too. repository.setDeskInactive(deskId) } } TRANSIT_TO_FRONT -> { logD("handleIndependentDeskTransitionIfNeeded desk=%d moved to front", deskId) change.isToTop() -> { val repository = desktopUserRepositories.getProfile(changeUserId) logD( "desk=%d of user=%d moved to front, " + "will let the organizer and repository activate", deskId, changeUserId, ) desksOrganizer.activateDesk(wct, deskId, skipReorder = true) repository.setActiveDesk(displayId, deskId) } else -> { logW( Loading @@ -259,9 +446,71 @@ class DesksTransitionObserver( } } if (wct.isEmpty) { logD("No changes, ignoring") return } transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) logD("handleIndependentDeskTransitionIfNeeded starting transition") mainScope.launch { transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) } } private fun getUserSwitch( change: TransitionInfo.Change, openingUserIds: List<Int>, ): UserSwitch? { val taskInfo = checkNotNull(change.taskInfo) { "Expected non-null task info" } val currentUserId = shellController.currentUserId val changeUserId = taskInfo.userId // This isn't a reliable way to identify user switches. It makes two assumptions: // 1) When switching from user A to user B with a desk active, the transition // change for the desk is reported with the id of user B - that is because root // tasks are not tied to a single user, so WM always sets the ID of the "current" // (post-switch) user. // 2) The transition is handled by Shell before Shell knows about the user switch // so |ShellController.userId| still reports user A. // TODO: b/418821815 - make desk ids unique even across users, or include user // change info in the transition. val isUserSwitch = currentUserId != changeUserId || // In some cases, like when switching users from one with an empty desk: // 1) there is no desk change because it was empty and thus already invisible // 2) the desktop wallpaper going to back has the userId of the new user (because // it uses |showForAllUsers|. // So the only way to identify the user switch is by looking at other tasks (of // the new user) that are opening/to-top, such as Home if say the new user is // is restoring to Home. openingUserIds.filterNot { it == currentUserId }.isNotEmpty() if (!isUserSwitch) return null val newUserId = if (currentUserId != changeUserId) { changeUserId } else { openingUserIds.filterNot { it == currentUserId }.first() } return UserSwitch(oldUserId = currentUserId, newUserId = newUserId) } data class UserSwitch(val oldUserId: Int, val newUserId: Int) private fun TransitionInfo.isRecentsType() = type == TRANSIT_START_RECENTS_TRANSITION || type == TRANSIT_END_RECENTS_TRANSITION private fun TransitionInfo.Change.isToTop(): Boolean = (mode == TRANSIT_TO_FRONT) || hasFlags(FLAG_MOVED_TO_TOP) private fun TransitionInfo.Change.isToBack(): Boolean = mode == TRANSIT_TO_BACK private fun TransitionInfo.taskChanges(): List<TransitionInfo.Change> = changes.filter { c -> val taskId = c.taskInfo?.taskId taskId != null && taskId != INVALID_TASK_ID } private fun TransitionInfo.deskChanges(): List<TransitionInfo.Change> = changes.filter { c -> desksOrganizer.isDeskChange(c) } private fun TransitionInfo.desktopWallpaperChanges(): List<TransitionInfo.Change> = changes.filter { c -> c.container == desktopWallpaperActivityTokenProvider.getToken(c.endDisplayId) } private fun logD(msg: String, vararg arguments: Any?) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt +12 −2 Original line number Diff line number Diff line Loading @@ -514,7 +514,12 @@ class RootTaskDesksOrganizer( if (deskRootsByDeskId.contains(taskInfo.taskId)) { val deskId = taskInfo.taskId deskRootsByDeskId[deskId] = deskRootsByDeskId[deskId].copy(taskInfo = taskInfo) logV("Desk #$deskId's task info changed in display#${taskInfo.displayId}") logV( "Desk #$deskId's task info changed in display#%d visible=%b children=%s", taskInfo.displayId, taskInfo.isVisible, deskRootsByDeskId[deskId].children, ) return } val minimizationRoot = Loading @@ -531,7 +536,12 @@ class RootTaskDesksOrganizer( if (parentTaskId in deskRootsByDeskId) { val deskId = taskInfo.parentTaskId val taskId = taskInfo.taskId logV("onTaskInfoChanged: Task #$taskId appeared in desk #$deskId") logV( "onTaskInfoChanged: Task #%d (visible=%b) appeared in desk #%d", taskId, taskInfo.isVisible, deskId, ) addChildToDesk(taskId = taskId, deskId = deskId) return } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +1 −2 Original line number Diff line number Diff line Loading @@ -279,8 +279,7 @@ public class ShellController { } /** Returns the current user id. */ @VisibleForTesting int getCurrentUserId() { public int getCurrentUserId() { return mUserId; } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt +460 −37 File changed.Preview size limit exceeded, changes collapsed. Show changes