Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +2 −0 Original line number Diff line number Diff line Loading @@ -1384,6 +1384,7 @@ public abstract class WMShellModule { ShellController shellController, DisplayController displayController, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, DesksOrganizer desksOrganizer, Optional<DesktopUserRepositories> desktopUserRepositories, Optional<DesktopTasksController> desktopTasksController, Optional<DesktopDisplayModeController> desktopDisplayModeController, Loading @@ -1401,6 +1402,7 @@ public abstract class WMShellModule { shellController, displayController, rootTaskDisplayAreaOrganizer, desksOrganizer, desktopRepositoryInitializer, desktopUserRepositories.get(), desktopTasksController.get(), Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt +35 −16 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import com.android.internal.protolog.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver import com.android.wm.shell.desktopmode.multidesks.OnDeskDisplayChangeListener import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener Loading @@ -44,6 +45,7 @@ class DesktopDisplayEventHandler( private val shellController: ShellController, private val displayController: DisplayController, private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, private val desksOrganizer: DesksOrganizer, private val desktopRepositoryInitializer: DesktopRepositoryInitializer, private val desktopUserRepositories: DesktopUserRepositories, private val desktopTasksController: DesktopTasksController, Loading Loading @@ -120,22 +122,19 @@ class DesktopDisplayEventHandler( val repository = userId?.let { desktopUserRepositories.getProfile(userId) } ?: desktopUserRepositories.current displayIds .filter { displayId -> displayId != Display.INVALID_DISPLAY } .filter { displayId -> supportsDesks(displayId) } .filter { displayId -> repository.getNumberOfDesks(displayId) == 0 } .also { displaysNeedingDesk -> logV( "createDefaultDesksIfNeeded creating default desks in displays=%s", displaysNeedingDesk, ) } .forEach { displayId -> for (displayId in displayIds) { if (!shouldCreateOrWarmUpDesk(displayId, repository)) continue if (isDisplayDesktopFirst(displayId)) { logV("Display %d is desktop-first and needs a default desk", displayId) desktopTasksController.createDesk( displayId, repository.userId, isDesktopFirstDisplay(displayId), displayId = displayId, userId = repository.userId, activateDesk = true, ) } else { logV("Display %d is touch-first and needs to warm up a desk", displayId) desksOrganizer.warmUpDefaultDesk(displayId, repository.userId) } } cancel() } Loading @@ -149,8 +148,28 @@ class DesktopDisplayEventHandler( desktopTasksController.onDeskDisconnectTransition(deskDisplayChanges) } // TODO: b/393978539 - implement this private fun isDesktopFirstDisplay(displayId: Int): Boolean = displayId != DEFAULT_DISPLAY private fun shouldCreateOrWarmUpDesk(displayId: Int, repository: DesktopRepository): Boolean { if (displayId == Display.INVALID_DISPLAY) { logV("shouldCreateOrWarmUpDesk skipping reason: invalid display") return false } if (!supportsDesks(displayId)) { logV( "shouldCreateOrWarmUpDesk skipping displayId=%d reason: desktop ineligible", displayId, ) return false } if (repository.getNumberOfDesks(displayId) > 0) { logV("shouldCreateOrWarmUpDesk skipping displayId=%d reason: has desk(s)", displayId) return false } return true } // TODO: b/362720497 - connected/projected display considerations. private fun isDisplayDesktopFirst(displayId: Int): Boolean = displayId != Display.DEFAULT_DISPLAY // TODO: b/362720497 - connected/projected display considerations. private fun supportsDesks(displayId: Int): Boolean = Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +100 −22 Original line number Diff line number Diff line Loading @@ -292,7 +292,9 @@ class DesktopTasksController( if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desktopRepositoryInitializer.deskRecreationFactory = DeskRecreationFactory { deskUserId, destinationDisplayId, _ -> createDeskSuspending(displayId = destinationDisplayId, userId = deskUserId) // TODO: b/393978539 - One of the recreated desks may need to be activated by // default in desktop-first. createDeskRootSuspending(displayId = destinationDisplayId, userId = deskUserId) } } } Loading Loading @@ -487,7 +489,7 @@ class DesktopTasksController( fun createDesk(displayId: Int, userId: Int = this.userId, activateDesk: Boolean = false) { logV("addDesk displayId=%d, userId=%d", displayId, userId) val repository = userRepositories.getProfile(userId) createDesk(displayId, userId) { deskId -> createDeskRoot(displayId, userId) { deskId -> if (deskId == null) { logW("Failed to add desk in displayId=%d for userId=%d", displayId, userId) } else { Loading @@ -499,7 +501,24 @@ class DesktopTasksController( } } private fun createDesk(displayId: Int, userId: Int = this.userId, onResult: (Int?) -> Unit) { @Deprecated("Use createDesk() instead.", ReplaceWith("createDesk()")) private fun createDeskImmediate(displayId: Int, userId: Int = this.userId): Int? { logV("createDeskImmediate displayId=%d, userId=%d", displayId, userId) val repository = userRepositories.getProfile(userId) val deskId = createDeskRootImmediate(displayId, userId) if (deskId == null) { logW("Failed to add desk in displayId=%d for userId=%d", displayId, userId) return null } repository.addDesk(displayId = displayId, deskId = deskId) return deskId } private fun createDeskRoot( displayId: Int, userId: Int = this.userId, onResult: (Int?) -> Unit, ) { if (displayId == Display.INVALID_DISPLAY) { logW("createDesk attempt with invalid displayId", displayId) onResult(null) Loading Loading @@ -530,9 +549,34 @@ class DesktopTasksController( } } private suspend fun createDeskSuspending(displayId: Int, userId: Int = this.userId): Int? = @Deprecated( "Use createDeskRootSuspending() instead.", ReplaceWith("createDeskRootSuspending()"), ) private fun createDeskRootImmediate(displayId: Int, userId: Int): Int? { if (displayId == Display.INVALID_DISPLAY) { logW("createDeskRootImmediate attempt with invalid displayId", displayId) return null } if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { // In single-desk, the desk reuses the display id. logD("createDeskRootImmediate reusing displayId=%d for single-desk", displayId) return displayId } if ( DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_HSUM.isTrue && UserManager.isHeadlessSystemUserMode() && UserHandle.USER_SYSTEM == userId ) { logW("createDeskRootImmediate ignoring attempt for system user") return null } return desksOrganizer.createDeskImmediate(displayId, userId) } private suspend fun createDeskRootSuspending(displayId: Int, userId: Int = this.userId): Int? = suspendCoroutine { cont -> createDesk(displayId, userId) { deskId -> cont.resumeWith(Result.success(deskId)) } createDeskRoot(displayId, userId) { deskId -> cont.resumeWith(Result.success(deskId)) } } /** Loading Loading @@ -609,7 +653,7 @@ class DesktopTasksController( logW("moveTaskToDefaultDeskAndActivate taskId=%d not found", taskId) return false } val deskId = getDefaultDeskId(task.displayId) val deskId = getOrCreateDefaultDeskId(task.displayId) ?: return false return moveTaskToDesk( taskId = taskId, deskId = deskId, Loading Loading @@ -668,7 +712,7 @@ class DesktopTasksController( return false } logV("moveBackgroundTaskToDesktop with taskId=%d", taskId) val deskId = getDefaultDeskId(task.displayId) val deskId = getOrCreateDefaultDeskId(task.displayId) ?: return false val runOnTransitStart = addDeskActivationChanges(deskId, wct, task) val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( Loading Loading @@ -793,7 +837,7 @@ class DesktopTasksController( * [startDragToDesktop]. */ private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo) { val deskId = getDefaultDeskId(taskInfo.displayId) val deskId = getOrCreateDefaultDeskId(taskInfo.displayId) ?: return ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: finalizeDragToDesktop taskId=%d deskId=%d", Loading Loading @@ -912,7 +956,7 @@ class DesktopTasksController( logW("minimizeTask: desk not found for task: ${taskInfo.taskId}") return } else { getDefaultDeskId(taskInfo.displayId) getOrCreateDefaultDeskId(taskInfo.displayId) } val isLastTask = if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { Loading Loading @@ -1168,7 +1212,10 @@ class DesktopTasksController( .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM } .toBundle(), ) val deskId = taskRepository.getDeskIdForTask(taskId) ?: getDefaultDeskId(DEFAULT_DISPLAY) val deskId = taskRepository.getDeskIdForTask(taskId) ?: getOrCreateDefaultDeskId(DEFAULT_DISPLAY) ?: return startLaunchTransition( TRANSIT_OPEN, wct, Loading @@ -1193,7 +1240,9 @@ class DesktopTasksController( unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN, ) { val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId) ?: getDefaultDeskId(taskInfo.displayId) taskRepository.getDeskIdForTask(taskInfo.taskId) ?: getOrCreateDefaultDeskId(taskInfo.displayId) ?: return logV("moveTaskToFront taskId=%s deskId=%s", taskInfo.taskId, deskId) // If a task is tiled, another task should be brought to foreground with it so let // tiling controller handle the request. Loading Loading @@ -1381,7 +1430,7 @@ class DesktopTasksController( } wct.sendPendingIntent(pendingIntent, intent, ops.toBundle()) val deskId = getDefaultDeskId(displayId) val deskId = getOrCreateDefaultDeskId(displayId) ?: return startLaunchTransition( TRANSIT_OPEN, wct, Loading Loading @@ -2332,7 +2381,7 @@ class DesktopTasksController( unminimizeReason = UnminimizeReason.APP_HANDLE_MENU_BUTTON, ) } else { val deskId = getDefaultDeskId(callingTask.displayId) val deskId = getOrCreateDefaultDeskId(callingTask.displayId) ?: return moveTaskToDesk( requestedTaskId, deskId, Loading Loading @@ -2403,7 +2452,8 @@ class DesktopTasksController( wct.sendPendingIntent(launchIntent, fillIn, options.toBundle()) val deskId = taskRepository.getDeskIdForTask(callingTaskInfo.taskId) ?: getDefaultDeskId(callingTaskInfo.displayId) ?: getOrCreateDefaultDeskId(callingTaskInfo.displayId) ?: return startLaunchTransition( transitionType = TRANSIT_OPEN, wct = wct, Loading Loading @@ -2518,7 +2568,7 @@ class DesktopTasksController( logV("skip keyguard is locked") return null } val deskId = getDefaultDeskId(task.displayId) val deskId = getOrCreateDefaultDeskId(task.displayId) ?: return null val isKnownDesktopTask = taskRepository.isActiveTask(task.taskId) val shouldEnterDesktop = forceEnterDesktop Loading Loading @@ -2633,7 +2683,7 @@ class DesktopTasksController( if (shouldFullscreenTaskLaunchSwitchToDesktop(task)) { logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId) return WindowContainerTransaction().also { wct -> val deskId = getDefaultDeskId(task.displayId) val deskId = getOrCreateDefaultDeskId(task.displayId) ?: return@also addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId) val runOnTransitStart: RunOnTransitStart? = if ( Loading Loading @@ -3128,7 +3178,7 @@ class DesktopTasksController( displayId: Int, remoteTransition: RemoteTransition? = null, ) { val deskId = getDefaultDeskId(displayId) val deskId = getOrCreateDefaultDeskId(displayId) ?: return activateDesk(deskId, remoteTransition) } Loading Loading @@ -3343,13 +3393,37 @@ class DesktopTasksController( /** Removes the default desk in the given display. */ @Deprecated("Deprecated with multi-desks.", ReplaceWith("removeDesk()")) fun removeDefaultDeskInDisplay(displayId: Int) { val deskId = getDefaultDeskId(displayId) val deskId = getOrCreateDefaultDeskId(displayId) ?: return removeDesk(displayId = displayId, deskId = deskId) } private fun getDefaultDeskId(displayId: Int) = checkNotNull(taskRepository.getDefaultDeskId(displayId)) { "Expected a default desk to exist in display: $displayId" /** * Returns the default desk if it exists, or creates it if needed. * * Note: [DesktopDisplayEventHandler] is responsible for creating a default desk in * desktop-first displays or warming up a desk-root in touch-first displays. This guarantees * that a non-null desk can be returned by this function because even if one does not exist yet, * [createDeskImmediate] should succeed. * * TODO: b/406890311 - replace with a suspending version that can wait for a desk to be created * from scratch before continuing their normal flow. */ @Deprecated("Use createDesk() instead", ReplaceWith("createDesk()")) private fun getOrCreateDefaultDeskId(displayId: Int): Int? { val existingDefaultDeskId = taskRepository.getDefaultDeskId(displayId) if (existingDefaultDeskId != null) { return existingDefaultDeskId } val immediateDeskId = createDeskImmediate(displayId, userId) if (immediateDeskId == null) { logE( "Failed to create immediate desk in displayId=%s for userId=%s:\n%s", displayId, userId, Throwable().stackTraceToString(), ) } return immediateDeskId } /** Removes the given desk. */ Loading Loading @@ -3883,7 +3957,7 @@ class DesktopTasksController( if (windowingMode == WINDOWING_MODE_FREEFORM) { if (DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue()) { // TODO b/376389593: Use a custom tab tearing transition/animation val deskId = getDefaultDeskId(DEFAULT_DISPLAY) val deskId = getOrCreateDefaultDeskId(DEFAULT_DISPLAY) ?: return false startLaunchTransition( TRANSIT_OPEN, wct, Loading Loading @@ -4280,6 +4354,10 @@ class DesktopTasksController( ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } private fun logE(msg: String, vararg arguments: Any?) { ProtoLog.e(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } companion object { @JvmField val DESKTOP_MODE_INITIAL_BOUNDS_SCALE = Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt +10 −0 Original line number Diff line number Diff line Loading @@ -21,9 +21,19 @@ import android.window.WindowContainerTransaction /** An organizer of desk containers in which to host child desktop windows. */ interface DesksOrganizer { /** Creates a new desk for the given user if none exist. */ fun warmUpDefaultDesk(displayId: Int, userId: Int) /** Creates a new desk container to use in the given display for the given user. */ fun createDesk(displayId: Int, userId: Int, callback: OnCreateCallback) /** * Creates and returns the id of a new desk container to use in the given display for the given * user if it can be created synchronously, or null if it cannot. */ @Deprecated("Use createDesk() instead.", ReplaceWith("createDesk()")) fun createDeskImmediate(displayId: Int, userId: Int): Int? /** Activates the given desk, making it visible in its display. */ fun activateDesk(wct: WindowContainerTransaction, deskId: Int) Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt +61 −11 Original line number Diff line number Diff line Loading @@ -73,26 +73,70 @@ class RootTaskDesksOrganizer( } } override fun warmUpDefaultDesk(displayId: Int, userId: Int) { logV("warmUpDefaultDesk in displayId=%d userId=%d", displayId, userId) // Check if a desk in this display is already created. deskRootsByDeskId.forEach { deskId, root -> if (root.taskInfo.displayId == displayId && deskId !in removeDeskRootRequests) { // A desk already exists. return } } val requestInProgress = createDeskRootRequests.any { request -> request.displayId == displayId } if (requestInProgress) { // There isn't one ready yet, but a request for one is already in progress. return } // Request a new one, but do not associate to the user. createDeskRoot(displayId, userId = null) { deskId -> logV("warmUpDefaultDesk created new desk root: %d", deskId) } } override fun createDesk(displayId: Int, userId: Int, callback: OnCreateCallback) { logV("createDesk in displayId=%d userId=%s", displayId, userId) // Find an existing desk that is not yet used by this user. val unassignedDesk = deskRootsByDeskId .valueIterator() .asSequence() .filterNot { desk -> userId in desk.users } .filterNot { desk -> desk.deskId in removeDeskRootRequests } .filter { desk -> desk.taskInfo.displayId == displayId } .firstOrNull() val unassignedDesk = firstUnassignedDesk(displayId, userId) if (unassignedDesk != null) { unassignedDesk.users.add(userId) callback.onCreated(unassignedDesk.deskId) return } // When there is an in-progress request without a user (as would be the case for a warm up // request), use that for this create request instead of creating another root. val unassignedRequest = createDeskRootRequests.firstOrNull { it.userId == null } if (unassignedRequest != null) { createDeskRootRequests.remove(unassignedRequest) createDeskRootRequests += unassignedRequest.copy(userId = userId) return } // Must request a new root. createDeskRoot(displayId, userId, callback) } private fun createDeskRoot(displayId: Int, userId: Int, callback: OnCreateCallback) { @Deprecated("Use createDesk() instead.", replaceWith = ReplaceWith("createDesk()")) override fun createDeskImmediate(displayId: Int, userId: Int): Int? { logV("createDeskImmediate in displayId=%d userId=%s", displayId, userId) // Find an existing desk that is not yet used by this user. val unassignedDesk = firstUnassignedDesk(displayId, userId) if (unassignedDesk != null) { unassignedDesk.users.add(userId) return unassignedDesk.deskId } return null } private fun firstUnassignedDesk(displayId: Int, userId: Int): DeskRoot? { return deskRootsByDeskId .valueIterator() .asSequence() .filterNot { desk -> userId in desk.users } .filterNot { desk -> desk.deskId in removeDeskRootRequests } .firstOrNull { desk -> desk.taskInfo.displayId == displayId } } private fun createDeskRoot(displayId: Int, userId: Int?, callback: OnCreateCallback) { logV("createDeskRoot in display: %d for user: %d", displayId, userId) createDeskRootRequests += CreateDeskRequest(displayId, userId, callback) shellTaskOrganizer.createRootTask( Loading Loading @@ -334,7 +378,12 @@ class RootTaskDesksOrganizer( deskId = deskId, taskInfo = taskInfo, leash = leash, users = mutableSetOf(deskRequest.userId), users = if (deskRequest.userId != null) { mutableSetOf(deskRequest.userId) } else { mutableSetOf() }, ) createDeskRootRequests.remove(deskRequest) deskRequest.onCreateCallback.onCreated(deskId) Loading Loading @@ -512,7 +561,7 @@ class RootTaskDesksOrganizer( private data class CreateDeskRequest( val displayId: Int, val userId: Int, val userId: Int?, val onCreateCallback: OnCreateCallback, ) Loading Loading @@ -542,6 +591,7 @@ class RootTaskDesksOrganizer( ) pw.println("${innerPrefix}createDeskRootRequests=$createDeskRootRequests") pw.println("${innerPrefix}removeDeskRootRequests=$removeDeskRootRequests") pw.println("${innerPrefix}numOfDeskRoots=${deskRootsByDeskId.size()}") pw.println("${innerPrefix}Desk Roots:") deskRootsByDeskId.forEach { deskId, root -> val minimizationRoot = deskMinimizationRootsByDeskId[deskId] Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +2 −0 Original line number Diff line number Diff line Loading @@ -1384,6 +1384,7 @@ public abstract class WMShellModule { ShellController shellController, DisplayController displayController, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, DesksOrganizer desksOrganizer, Optional<DesktopUserRepositories> desktopUserRepositories, Optional<DesktopTasksController> desktopTasksController, Optional<DesktopDisplayModeController> desktopDisplayModeController, Loading @@ -1401,6 +1402,7 @@ public abstract class WMShellModule { shellController, displayController, rootTaskDisplayAreaOrganizer, desksOrganizer, desktopRepositoryInitializer, desktopUserRepositories.get(), desktopTasksController.get(), Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt +35 −16 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import com.android.internal.protolog.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver import com.android.wm.shell.desktopmode.multidesks.OnDeskDisplayChangeListener import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener Loading @@ -44,6 +45,7 @@ class DesktopDisplayEventHandler( private val shellController: ShellController, private val displayController: DisplayController, private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, private val desksOrganizer: DesksOrganizer, private val desktopRepositoryInitializer: DesktopRepositoryInitializer, private val desktopUserRepositories: DesktopUserRepositories, private val desktopTasksController: DesktopTasksController, Loading Loading @@ -120,22 +122,19 @@ class DesktopDisplayEventHandler( val repository = userId?.let { desktopUserRepositories.getProfile(userId) } ?: desktopUserRepositories.current displayIds .filter { displayId -> displayId != Display.INVALID_DISPLAY } .filter { displayId -> supportsDesks(displayId) } .filter { displayId -> repository.getNumberOfDesks(displayId) == 0 } .also { displaysNeedingDesk -> logV( "createDefaultDesksIfNeeded creating default desks in displays=%s", displaysNeedingDesk, ) } .forEach { displayId -> for (displayId in displayIds) { if (!shouldCreateOrWarmUpDesk(displayId, repository)) continue if (isDisplayDesktopFirst(displayId)) { logV("Display %d is desktop-first and needs a default desk", displayId) desktopTasksController.createDesk( displayId, repository.userId, isDesktopFirstDisplay(displayId), displayId = displayId, userId = repository.userId, activateDesk = true, ) } else { logV("Display %d is touch-first and needs to warm up a desk", displayId) desksOrganizer.warmUpDefaultDesk(displayId, repository.userId) } } cancel() } Loading @@ -149,8 +148,28 @@ class DesktopDisplayEventHandler( desktopTasksController.onDeskDisconnectTransition(deskDisplayChanges) } // TODO: b/393978539 - implement this private fun isDesktopFirstDisplay(displayId: Int): Boolean = displayId != DEFAULT_DISPLAY private fun shouldCreateOrWarmUpDesk(displayId: Int, repository: DesktopRepository): Boolean { if (displayId == Display.INVALID_DISPLAY) { logV("shouldCreateOrWarmUpDesk skipping reason: invalid display") return false } if (!supportsDesks(displayId)) { logV( "shouldCreateOrWarmUpDesk skipping displayId=%d reason: desktop ineligible", displayId, ) return false } if (repository.getNumberOfDesks(displayId) > 0) { logV("shouldCreateOrWarmUpDesk skipping displayId=%d reason: has desk(s)", displayId) return false } return true } // TODO: b/362720497 - connected/projected display considerations. private fun isDisplayDesktopFirst(displayId: Int): Boolean = displayId != Display.DEFAULT_DISPLAY // TODO: b/362720497 - connected/projected display considerations. private fun supportsDesks(displayId: Int): Boolean = Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +100 −22 Original line number Diff line number Diff line Loading @@ -292,7 +292,9 @@ class DesktopTasksController( if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desktopRepositoryInitializer.deskRecreationFactory = DeskRecreationFactory { deskUserId, destinationDisplayId, _ -> createDeskSuspending(displayId = destinationDisplayId, userId = deskUserId) // TODO: b/393978539 - One of the recreated desks may need to be activated by // default in desktop-first. createDeskRootSuspending(displayId = destinationDisplayId, userId = deskUserId) } } } Loading Loading @@ -487,7 +489,7 @@ class DesktopTasksController( fun createDesk(displayId: Int, userId: Int = this.userId, activateDesk: Boolean = false) { logV("addDesk displayId=%d, userId=%d", displayId, userId) val repository = userRepositories.getProfile(userId) createDesk(displayId, userId) { deskId -> createDeskRoot(displayId, userId) { deskId -> if (deskId == null) { logW("Failed to add desk in displayId=%d for userId=%d", displayId, userId) } else { Loading @@ -499,7 +501,24 @@ class DesktopTasksController( } } private fun createDesk(displayId: Int, userId: Int = this.userId, onResult: (Int?) -> Unit) { @Deprecated("Use createDesk() instead.", ReplaceWith("createDesk()")) private fun createDeskImmediate(displayId: Int, userId: Int = this.userId): Int? { logV("createDeskImmediate displayId=%d, userId=%d", displayId, userId) val repository = userRepositories.getProfile(userId) val deskId = createDeskRootImmediate(displayId, userId) if (deskId == null) { logW("Failed to add desk in displayId=%d for userId=%d", displayId, userId) return null } repository.addDesk(displayId = displayId, deskId = deskId) return deskId } private fun createDeskRoot( displayId: Int, userId: Int = this.userId, onResult: (Int?) -> Unit, ) { if (displayId == Display.INVALID_DISPLAY) { logW("createDesk attempt with invalid displayId", displayId) onResult(null) Loading Loading @@ -530,9 +549,34 @@ class DesktopTasksController( } } private suspend fun createDeskSuspending(displayId: Int, userId: Int = this.userId): Int? = @Deprecated( "Use createDeskRootSuspending() instead.", ReplaceWith("createDeskRootSuspending()"), ) private fun createDeskRootImmediate(displayId: Int, userId: Int): Int? { if (displayId == Display.INVALID_DISPLAY) { logW("createDeskRootImmediate attempt with invalid displayId", displayId) return null } if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { // In single-desk, the desk reuses the display id. logD("createDeskRootImmediate reusing displayId=%d for single-desk", displayId) return displayId } if ( DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_HSUM.isTrue && UserManager.isHeadlessSystemUserMode() && UserHandle.USER_SYSTEM == userId ) { logW("createDeskRootImmediate ignoring attempt for system user") return null } return desksOrganizer.createDeskImmediate(displayId, userId) } private suspend fun createDeskRootSuspending(displayId: Int, userId: Int = this.userId): Int? = suspendCoroutine { cont -> createDesk(displayId, userId) { deskId -> cont.resumeWith(Result.success(deskId)) } createDeskRoot(displayId, userId) { deskId -> cont.resumeWith(Result.success(deskId)) } } /** Loading Loading @@ -609,7 +653,7 @@ class DesktopTasksController( logW("moveTaskToDefaultDeskAndActivate taskId=%d not found", taskId) return false } val deskId = getDefaultDeskId(task.displayId) val deskId = getOrCreateDefaultDeskId(task.displayId) ?: return false return moveTaskToDesk( taskId = taskId, deskId = deskId, Loading Loading @@ -668,7 +712,7 @@ class DesktopTasksController( return false } logV("moveBackgroundTaskToDesktop with taskId=%d", taskId) val deskId = getDefaultDeskId(task.displayId) val deskId = getOrCreateDefaultDeskId(task.displayId) ?: return false val runOnTransitStart = addDeskActivationChanges(deskId, wct, task) val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( Loading Loading @@ -793,7 +837,7 @@ class DesktopTasksController( * [startDragToDesktop]. */ private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo) { val deskId = getDefaultDeskId(taskInfo.displayId) val deskId = getOrCreateDefaultDeskId(taskInfo.displayId) ?: return ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: finalizeDragToDesktop taskId=%d deskId=%d", Loading Loading @@ -912,7 +956,7 @@ class DesktopTasksController( logW("minimizeTask: desk not found for task: ${taskInfo.taskId}") return } else { getDefaultDeskId(taskInfo.displayId) getOrCreateDefaultDeskId(taskInfo.displayId) } val isLastTask = if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { Loading Loading @@ -1168,7 +1212,10 @@ class DesktopTasksController( .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM } .toBundle(), ) val deskId = taskRepository.getDeskIdForTask(taskId) ?: getDefaultDeskId(DEFAULT_DISPLAY) val deskId = taskRepository.getDeskIdForTask(taskId) ?: getOrCreateDefaultDeskId(DEFAULT_DISPLAY) ?: return startLaunchTransition( TRANSIT_OPEN, wct, Loading @@ -1193,7 +1240,9 @@ class DesktopTasksController( unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN, ) { val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId) ?: getDefaultDeskId(taskInfo.displayId) taskRepository.getDeskIdForTask(taskInfo.taskId) ?: getOrCreateDefaultDeskId(taskInfo.displayId) ?: return logV("moveTaskToFront taskId=%s deskId=%s", taskInfo.taskId, deskId) // If a task is tiled, another task should be brought to foreground with it so let // tiling controller handle the request. Loading Loading @@ -1381,7 +1430,7 @@ class DesktopTasksController( } wct.sendPendingIntent(pendingIntent, intent, ops.toBundle()) val deskId = getDefaultDeskId(displayId) val deskId = getOrCreateDefaultDeskId(displayId) ?: return startLaunchTransition( TRANSIT_OPEN, wct, Loading Loading @@ -2332,7 +2381,7 @@ class DesktopTasksController( unminimizeReason = UnminimizeReason.APP_HANDLE_MENU_BUTTON, ) } else { val deskId = getDefaultDeskId(callingTask.displayId) val deskId = getOrCreateDefaultDeskId(callingTask.displayId) ?: return moveTaskToDesk( requestedTaskId, deskId, Loading Loading @@ -2403,7 +2452,8 @@ class DesktopTasksController( wct.sendPendingIntent(launchIntent, fillIn, options.toBundle()) val deskId = taskRepository.getDeskIdForTask(callingTaskInfo.taskId) ?: getDefaultDeskId(callingTaskInfo.displayId) ?: getOrCreateDefaultDeskId(callingTaskInfo.displayId) ?: return startLaunchTransition( transitionType = TRANSIT_OPEN, wct = wct, Loading Loading @@ -2518,7 +2568,7 @@ class DesktopTasksController( logV("skip keyguard is locked") return null } val deskId = getDefaultDeskId(task.displayId) val deskId = getOrCreateDefaultDeskId(task.displayId) ?: return null val isKnownDesktopTask = taskRepository.isActiveTask(task.taskId) val shouldEnterDesktop = forceEnterDesktop Loading Loading @@ -2633,7 +2683,7 @@ class DesktopTasksController( if (shouldFullscreenTaskLaunchSwitchToDesktop(task)) { logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId) return WindowContainerTransaction().also { wct -> val deskId = getDefaultDeskId(task.displayId) val deskId = getOrCreateDefaultDeskId(task.displayId) ?: return@also addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId) val runOnTransitStart: RunOnTransitStart? = if ( Loading Loading @@ -3128,7 +3178,7 @@ class DesktopTasksController( displayId: Int, remoteTransition: RemoteTransition? = null, ) { val deskId = getDefaultDeskId(displayId) val deskId = getOrCreateDefaultDeskId(displayId) ?: return activateDesk(deskId, remoteTransition) } Loading Loading @@ -3343,13 +3393,37 @@ class DesktopTasksController( /** Removes the default desk in the given display. */ @Deprecated("Deprecated with multi-desks.", ReplaceWith("removeDesk()")) fun removeDefaultDeskInDisplay(displayId: Int) { val deskId = getDefaultDeskId(displayId) val deskId = getOrCreateDefaultDeskId(displayId) ?: return removeDesk(displayId = displayId, deskId = deskId) } private fun getDefaultDeskId(displayId: Int) = checkNotNull(taskRepository.getDefaultDeskId(displayId)) { "Expected a default desk to exist in display: $displayId" /** * Returns the default desk if it exists, or creates it if needed. * * Note: [DesktopDisplayEventHandler] is responsible for creating a default desk in * desktop-first displays or warming up a desk-root in touch-first displays. This guarantees * that a non-null desk can be returned by this function because even if one does not exist yet, * [createDeskImmediate] should succeed. * * TODO: b/406890311 - replace with a suspending version that can wait for a desk to be created * from scratch before continuing their normal flow. */ @Deprecated("Use createDesk() instead", ReplaceWith("createDesk()")) private fun getOrCreateDefaultDeskId(displayId: Int): Int? { val existingDefaultDeskId = taskRepository.getDefaultDeskId(displayId) if (existingDefaultDeskId != null) { return existingDefaultDeskId } val immediateDeskId = createDeskImmediate(displayId, userId) if (immediateDeskId == null) { logE( "Failed to create immediate desk in displayId=%s for userId=%s:\n%s", displayId, userId, Throwable().stackTraceToString(), ) } return immediateDeskId } /** Removes the given desk. */ Loading Loading @@ -3883,7 +3957,7 @@ class DesktopTasksController( if (windowingMode == WINDOWING_MODE_FREEFORM) { if (DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue()) { // TODO b/376389593: Use a custom tab tearing transition/animation val deskId = getDefaultDeskId(DEFAULT_DISPLAY) val deskId = getOrCreateDefaultDeskId(DEFAULT_DISPLAY) ?: return false startLaunchTransition( TRANSIT_OPEN, wct, Loading Loading @@ -4280,6 +4354,10 @@ class DesktopTasksController( ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } private fun logE(msg: String, vararg arguments: Any?) { ProtoLog.e(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } companion object { @JvmField val DESKTOP_MODE_INITIAL_BOUNDS_SCALE = Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt +10 −0 Original line number Diff line number Diff line Loading @@ -21,9 +21,19 @@ import android.window.WindowContainerTransaction /** An organizer of desk containers in which to host child desktop windows. */ interface DesksOrganizer { /** Creates a new desk for the given user if none exist. */ fun warmUpDefaultDesk(displayId: Int, userId: Int) /** Creates a new desk container to use in the given display for the given user. */ fun createDesk(displayId: Int, userId: Int, callback: OnCreateCallback) /** * Creates and returns the id of a new desk container to use in the given display for the given * user if it can be created synchronously, or null if it cannot. */ @Deprecated("Use createDesk() instead.", ReplaceWith("createDesk()")) fun createDeskImmediate(displayId: Int, userId: Int): Int? /** Activates the given desk, making it visible in its display. */ fun activateDesk(wct: WindowContainerTransaction, deskId: Int) Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt +61 −11 Original line number Diff line number Diff line Loading @@ -73,26 +73,70 @@ class RootTaskDesksOrganizer( } } override fun warmUpDefaultDesk(displayId: Int, userId: Int) { logV("warmUpDefaultDesk in displayId=%d userId=%d", displayId, userId) // Check if a desk in this display is already created. deskRootsByDeskId.forEach { deskId, root -> if (root.taskInfo.displayId == displayId && deskId !in removeDeskRootRequests) { // A desk already exists. return } } val requestInProgress = createDeskRootRequests.any { request -> request.displayId == displayId } if (requestInProgress) { // There isn't one ready yet, but a request for one is already in progress. return } // Request a new one, but do not associate to the user. createDeskRoot(displayId, userId = null) { deskId -> logV("warmUpDefaultDesk created new desk root: %d", deskId) } } override fun createDesk(displayId: Int, userId: Int, callback: OnCreateCallback) { logV("createDesk in displayId=%d userId=%s", displayId, userId) // Find an existing desk that is not yet used by this user. val unassignedDesk = deskRootsByDeskId .valueIterator() .asSequence() .filterNot { desk -> userId in desk.users } .filterNot { desk -> desk.deskId in removeDeskRootRequests } .filter { desk -> desk.taskInfo.displayId == displayId } .firstOrNull() val unassignedDesk = firstUnassignedDesk(displayId, userId) if (unassignedDesk != null) { unassignedDesk.users.add(userId) callback.onCreated(unassignedDesk.deskId) return } // When there is an in-progress request without a user (as would be the case for a warm up // request), use that for this create request instead of creating another root. val unassignedRequest = createDeskRootRequests.firstOrNull { it.userId == null } if (unassignedRequest != null) { createDeskRootRequests.remove(unassignedRequest) createDeskRootRequests += unassignedRequest.copy(userId = userId) return } // Must request a new root. createDeskRoot(displayId, userId, callback) } private fun createDeskRoot(displayId: Int, userId: Int, callback: OnCreateCallback) { @Deprecated("Use createDesk() instead.", replaceWith = ReplaceWith("createDesk()")) override fun createDeskImmediate(displayId: Int, userId: Int): Int? { logV("createDeskImmediate in displayId=%d userId=%s", displayId, userId) // Find an existing desk that is not yet used by this user. val unassignedDesk = firstUnassignedDesk(displayId, userId) if (unassignedDesk != null) { unassignedDesk.users.add(userId) return unassignedDesk.deskId } return null } private fun firstUnassignedDesk(displayId: Int, userId: Int): DeskRoot? { return deskRootsByDeskId .valueIterator() .asSequence() .filterNot { desk -> userId in desk.users } .filterNot { desk -> desk.deskId in removeDeskRootRequests } .firstOrNull { desk -> desk.taskInfo.displayId == displayId } } private fun createDeskRoot(displayId: Int, userId: Int?, callback: OnCreateCallback) { logV("createDeskRoot in display: %d for user: %d", displayId, userId) createDeskRootRequests += CreateDeskRequest(displayId, userId, callback) shellTaskOrganizer.createRootTask( Loading Loading @@ -334,7 +378,12 @@ class RootTaskDesksOrganizer( deskId = deskId, taskInfo = taskInfo, leash = leash, users = mutableSetOf(deskRequest.userId), users = if (deskRequest.userId != null) { mutableSetOf(deskRequest.userId) } else { mutableSetOf() }, ) createDeskRootRequests.remove(deskRequest) deskRequest.onCreateCallback.onCreated(deskId) Loading Loading @@ -512,7 +561,7 @@ class RootTaskDesksOrganizer( private data class CreateDeskRequest( val displayId: Int, val userId: Int, val userId: Int?, val onCreateCallback: OnCreateCallback, ) Loading Loading @@ -542,6 +591,7 @@ class RootTaskDesksOrganizer( ) pw.println("${innerPrefix}createDeskRootRequests=$createDeskRootRequests") pw.println("${innerPrefix}removeDeskRootRequests=$removeDeskRootRequests") pw.println("${innerPrefix}numOfDeskRoots=${deskRootsByDeskId.size()}") pw.println("${innerPrefix}Desk Roots:") deskRootsByDeskId.forEach { deskId, root -> val minimizationRoot = deskMinimizationRootsByDeskId[deskId] Loading