Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 06bfc124 authored by Jorge Gil's avatar Jorge Gil
Browse files

Desks: Allow pre-creating desk roots without creating the desk

Instead of always creating a default desk, which was necessary because
a desk root needs to be available when moving into desktop for the first
time (because creating desk roots is slow / async), this change changes
that logic to only pre-create the desk root, without letting the
DesktopRepository know about any first/default desk being created.

This fixes an issue in touch-first displays that should not show an
empty/default desk by default on boot, and should instead only show it
after explicitly entering desktop.

To allow creating the "real" desk (the one seen by DesktopRepository) in
sync, this change introduces a createDeskImmediate() function that
assumes a default desk can be created in sync because a root has been
pre-created already. This a short-term bandaid because many callpoints
require a default desk to be available synchronously. Long term, callers
should be refactored to be able to request and wait for a default desk
to be created before continuing their normal flow.

Flag: com.android.window.flags.enable_multiple_desktops_backend
Bug: 399394443
Test: check root is precreated on boot in tablets, but the desk isn't -
then enter desktop and check the desk was created with the pre-created
root.

Change-Id: I50d5c16d4c81f0aae065f7b1139f8f15e03963ef
parent b9232c16
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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,
@@ -1401,6 +1402,7 @@ public abstract class WMShellModule {
                        shellController,
                        displayController,
                        rootTaskDisplayAreaOrganizer,
                        desksOrganizer,
                        desktopRepositoryInitializer,
                        desktopUserRepositories.get(),
                        desktopTasksController.get(),
+35 −16
Original line number Diff line number Diff line
@@ -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
@@ -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,
@@ -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()
            }
@@ -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 =
+100 −22
Original line number Diff line number Diff line
@@ -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)
                }
        }
    }
@@ -486,7 +488,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 {
@@ -498,7 +500,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)
@@ -529,9 +548,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)) }
        }

    /**
@@ -608,7 +652,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,
@@ -667,7 +711,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(
@@ -792,7 +836,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",
@@ -911,7 +955,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) {
@@ -1167,7 +1211,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,
@@ -1192,7 +1239,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.
@@ -1380,7 +1429,7 @@ class DesktopTasksController(
            }

        wct.sendPendingIntent(pendingIntent, intent, ops.toBundle())
        val deskId = getDefaultDeskId(displayId)
        val deskId = getOrCreateDefaultDeskId(displayId) ?: return
        startLaunchTransition(
            TRANSIT_OPEN,
            wct,
@@ -2322,7 +2371,7 @@ class DesktopTasksController(
                    unminimizeReason = UnminimizeReason.APP_HANDLE_MENU_BUTTON,
                )
            } else {
                val deskId = getDefaultDeskId(callingTask.displayId)
                val deskId = getOrCreateDefaultDeskId(callingTask.displayId) ?: return
                moveTaskToDesk(
                    requestedTaskId,
                    deskId,
@@ -2393,7 +2442,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,
@@ -2486,7 +2536,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
@@ -2601,7 +2651,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 (
@@ -3096,7 +3146,7 @@ class DesktopTasksController(
        displayId: Int,
        remoteTransition: RemoteTransition? = null,
    ) {
        val deskId = getDefaultDeskId(displayId)
        val deskId = getOrCreateDefaultDeskId(displayId) ?: return
        activateDesk(deskId, remoteTransition)
    }

@@ -3311,13 +3361,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. */
@@ -3851,7 +3925,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,
@@ -4248,6 +4322,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 =
+10 −0
Original line number Diff line number Diff line
@@ -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)

+61 −11
Original line number Diff line number Diff line
@@ -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(
@@ -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)
@@ -512,7 +561,7 @@ class RootTaskDesksOrganizer(

    private data class CreateDeskRequest(
        val displayId: Int,
        val userId: Int,
        val userId: Int?,
        val onCreateCallback: OnCreateCallback,
    )

@@ -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