Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt +9 −13 Original line number Diff line number Diff line Loading @@ -49,9 +49,6 @@ class DesktopDisplayEventHandler( private val desktopDisplayModeController: DesktopDisplayModeController, ) : OnDisplaysChangedListener, OnDeskRemovedListener { private val desktopRepository: DesktopRepository get() = desktopUserRepositories.current init { shellInit.addInitCallback({ onInit() }, this) } Loading @@ -66,7 +63,7 @@ class DesktopDisplayEventHandler( object : UserChangeListener { override fun onUserChanged(newUserId: Int, userContext: Context) { val displayIds = rootTaskDisplayAreaOrganizer.displayIds createDefaultDesksIfNeeded(displayIds.toSet()) createDefaultDesksIfNeeded(displayIds.toSet(), newUserId) } } ) Loading @@ -78,7 +75,7 @@ class DesktopDisplayEventHandler( desktopDisplayModeController.refreshDisplayWindowingMode() } createDefaultDesksIfNeeded(displayIds = setOf(displayId)) createDefaultDesksIfNeeded(displayIds = setOf(displayId), userId = null) } override fun onDisplayRemoved(displayId: Int) { Loading @@ -99,23 +96,22 @@ class DesktopDisplayEventHandler( } override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) { val remainingDesks = desktopRepository.getNumberOfDesks(lastDisplayId) if (remainingDesks == 0) { logV("All desks removed from display#$lastDisplayId") createDefaultDesksIfNeeded(setOf(lastDisplayId)) } createDefaultDesksIfNeeded(setOf(lastDisplayId), userId = null) } private fun createDefaultDesksIfNeeded(displayIds: Set<Int>) { private fun createDefaultDesksIfNeeded(displayIds: Set<Int>, userId: Int?) { if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return logV("createDefaultDesksIfNeeded displays=%s", displayIds) mainScope.launch { desktopRepositoryInitializer.isInitialized.collect { initialized -> if (!initialized) return@collect val repository = userId?.let { desktopUserRepositories.getProfile(userId) } ?: desktopUserRepositories.current displayIds .filter { displayId -> displayId != Display.INVALID_DISPLAY } .filter { displayId -> supportsDesks(displayId) } .filter { displayId -> desktopRepository.getNumberOfDesks(displayId) == 0 } .filter { displayId -> repository.getNumberOfDesks(displayId) == 0 } .also { displaysNeedingDesk -> logV( "createDefaultDesksIfNeeded creating default desks in displays=%s", Loading @@ -125,7 +121,7 @@ class DesktopDisplayEventHandler( .forEach { displayId -> // TODO: b/393978539 - consider activating the desk on creation when // applicable, such as for connected displays. desktopTasksController.createDesk(displayId) desktopTasksController.createDesk(displayId, repository.userId) } cancel() } Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +55 −28 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import android.os.Handler import android.os.IBinder import android.os.SystemProperties import android.os.UserHandle import android.os.UserManager import android.util.Slog import android.view.Display import android.view.Display.DEFAULT_DISPLAY Loading Loading @@ -115,7 +116,6 @@ import com.android.wm.shell.desktopmode.multidesks.DeskTransition import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener import com.android.wm.shell.desktopmode.multidesks.createDesk import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer.DeskRecreationFactory import com.android.wm.shell.draganddrop.DragAndDropController Loading Loading @@ -163,6 +163,7 @@ import java.util.Optional import java.util.concurrent.Executor import java.util.concurrent.TimeUnit import java.util.function.Consumer import kotlin.coroutines.suspendCoroutine import kotlin.jvm.optionals.getOrNull /** Loading Loading @@ -283,14 +284,8 @@ class DesktopTasksController( if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desktopRepositoryInitializer.deskRecreationFactory = DeskRecreationFactory { deskUserId, destinationDisplayId, deskId -> if (deskUserId != userId) { // TODO: b/400984250 - add multi-user support for multi-desk restoration. logW("Tried to re-create desk of another user.") null } else { desksOrganizer.createDesk(destinationDisplayId) } DeskRecreationFactory { deskUserId, destinationDisplayId, _ -> createDeskSuspending(displayId = destinationDisplayId, userId = deskUserId) } } } Loading Loading @@ -493,20 +488,53 @@ class DesktopTasksController( runOnTransitStart?.invoke(transition) } /** Creates a new desk in the given display. */ fun createDesk(displayId: Int) { /** Adds a new desk to the given display for the given user. */ fun createDesk(displayId: Int, userId: Int = this.userId) { logV("addDesk displayId=%d, userId=%d", displayId, userId) val repository = userRepositories.getProfile(userId) createDesk(displayId, userId) { deskId -> if (deskId == null) { logW("Failed to add desk in displayId=%d for userId=%d", displayId, userId) } else { repository.addDesk(displayId = displayId, deskId = deskId) } } } private fun createDesk(displayId: Int, userId: Int = this.userId, onResult: (Int?) -> Unit) { if (displayId == Display.INVALID_DISPLAY) { logW("createDesk attempt with invalid displayId", displayId) onResult(null) return } if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desksOrganizer.createDesk(displayId) { deskId -> taskRepository.addDesk(displayId = displayId, deskId = deskId) } } else { if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { // In single-desk, the desk reuses the display id. taskRepository.addDesk(displayId = displayId, deskId = displayId) logD("createDesk reusing displayId=%d for single-desk", displayId) onResult(displayId) return } if ( DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_HSUM.isTrue && UserManager.isHeadlessSystemUserMode() && UserHandle.USER_SYSTEM == userId ) { logW("createDesk ignoring attempt for system user") return } desksOrganizer.createDesk(displayId, userId) { deskId -> logD( "createDesk obtained deskId=%d for displayId=%d and userId=%d", deskId, displayId, userId, ) onResult(deskId) } } private suspend fun createDeskSuspending(displayId: Int, userId: Int = this.userId): Int? = suspendCoroutine { cont -> createDesk(displayId, userId) { deskId -> cont.resumeWith(Result.success(deskId)) } } /** Moves task to desktop mode if task is running, else launches it in desktop mode. */ Loading Loading @@ -3024,8 +3052,8 @@ class DesktopTasksController( } val wct = WindowContainerTransaction() if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { tasksToRemove.forEach { // TODO: b/404595635 - consider moving this block into [DesksOrganizer]. val task = shellTaskOrganizer.getRunningTaskInfo(it) if (task != null) { wct.removeTask(task.token) Loading @@ -3033,9 +3061,8 @@ class DesktopTasksController( recentTasksController?.removeBackgroundTask(it) } } } else { // TODO: 362720497 - double check background tasks are also removed. desksOrganizer.removeDesk(wct, deskId) if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desksOrganizer.removeDesk(wct, deskId, userId) } if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue && wct.isEmpty) return val transition = transitions.startTransition(TRANSIT_CLOSE, wct, /* handler= */ null) Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt +4 −12 Original line number Diff line number Diff line Loading @@ -18,13 +18,11 @@ package com.android.wm.shell.desktopmode.multidesks import android.app.ActivityManager import android.window.TransitionInfo import android.window.WindowContainerTransaction import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer.OnCreateCallback import kotlin.coroutines.suspendCoroutine /** An organizer of desk containers in which to host child desktop windows. */ interface DesksOrganizer { /** Creates a new desk container in the given display. */ fun createDesk(displayId: Int, callback: OnCreateCallback) /** Creates a new desk container to use in the given display for the given user. */ fun createDesk(displayId: Int, userId: Int, callback: OnCreateCallback) /** Activates the given desk, making it visible in its display. */ fun activateDesk(wct: WindowContainerTransaction, deskId: Int) Loading @@ -32,8 +30,8 @@ interface DesksOrganizer { /** Deactivates the given desk, removing it as the default launch container for new tasks. */ fun deactivateDesk(wct: WindowContainerTransaction, deskId: Int) /** Removes the given desk and its desktop windows. */ fun removeDesk(wct: WindowContainerTransaction, deskId: Int) /** Removes the given desk of the given user. */ fun removeDesk(wct: WindowContainerTransaction, deskId: Int, userId: Int) /** Moves the given task to the given desk. */ fun moveTaskToDesk( Loading Loading @@ -87,9 +85,3 @@ interface DesksOrganizer { fun onCreated(deskId: Int) } } /** Creates a new desk container in the given display. */ suspend fun DesksOrganizer.createDesk(displayId: Int): Int = suspendCoroutine { cont -> val onCreateCallback = OnCreateCallback { deskId -> cont.resumeWith(Result.success(deskId)) } createDesk(displayId, onCreateCallback) } libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt +83 −19 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.window.TransitionInfo import android.window.WindowContainerToken import android.window.WindowContainerTransaction import androidx.core.util.forEach import androidx.core.util.valueIterator import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer Loading @@ -40,7 +41,13 @@ import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellInit import java.io.PrintWriter /** A [DesksOrganizer] that uses root tasks as the container of each desk. */ /** * A [DesksOrganizer] that uses root tasks as the container of each desk. * * Note that root tasks are reusable between multiple users at the same time, and may also be * pre-created to have one ready for the first entry to the default desk, so root-task existence * does not imply a formal desk exists to the user. */ class RootTaskDesksOrganizer( shellInit: ShellInit, shellCommandHandler: ShellCommandHandler, Loading @@ -65,9 +72,26 @@ class RootTaskDesksOrganizer( } } override fun createDesk(displayId: Int, callback: OnCreateCallback) { logV("createDesk in display: %d", displayId) createDeskRootRequests += CreateDeskRequest(displayId, callback) 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 } .firstOrNull() if (unassignedDesk != null) { unassignedDesk.users.add(userId) callback.onCreated(unassignedDesk.deskId) return } createDeskRoot(displayId, userId, callback) } 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( displayId, WINDOWING_MODE_FREEFORM, Loading @@ -76,32 +100,53 @@ class RootTaskDesksOrganizer( ) } override fun removeDesk(wct: WindowContainerTransaction, deskId: Int) { logV("removeDesk %d", deskId) deskRootsByDeskId[deskId]?.let { root -> wct.removeRootTask(root.token) } override fun removeDesk(wct: WindowContainerTransaction, deskId: Int, userId: Int) { logV("removeDesk %d for userId=%d", deskId, userId) val deskRoot = deskRootsByDeskId[deskId] if (deskRoot == null) { logW("removeDesk attempted to remove non-existent desk=%d", deskId) return } updateLaunchRoot(wct, deskId, enabled = false) deskRoot.users.remove(userId) if (deskRoot.users.isEmpty()) { // No longer in use by any users, remove it completely. logD("removeDesk %d is no longer used by any users, removing it completely", deskId) wct.removeRootTask(deskRoot.token) deskMinimizationRootsByDeskId[deskId]?.let { root -> wct.removeRootTask(root.token) } } } override fun activateDesk(wct: WindowContainerTransaction, deskId: Int) { logV("activateDesk %d", deskId) val root = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" } wct.reorder(root.token, /* onTop= */ true) wct.setLaunchRoot( /* container= */ root.taskInfo.token, /* windowingModes= */ intArrayOf(WINDOWING_MODE_FREEFORM, WINDOWING_MODE_UNDEFINED), /* activityTypes= */ intArrayOf(ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD), ) updateLaunchRoot(wct, deskId, enabled = true) } override fun deactivateDesk(wct: WindowContainerTransaction, deskId: Int) { logV("deactivateDesk %d", deskId) updateLaunchRoot(wct, deskId, enabled = false) } private fun updateLaunchRoot(wct: WindowContainerTransaction, deskId: Int, enabled: Boolean) { val root = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" } root.isLaunchRootRequested = enabled logD("updateLaunchRoot deskId=%d enabled=%b", deskId, enabled) if (enabled) { wct.setLaunchRoot( /* container= */ root.taskInfo.token, /* windowingModes= */ intArrayOf(WINDOWING_MODE_FREEFORM, WINDOWING_MODE_UNDEFINED), /* activityTypes= */ intArrayOf(ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD), ) } else { wct.setLaunchRoot( /* container= */ root.taskInfo.token, /* windowingModes= */ null, /* activityTypes= */ null, ) } } override fun moveTaskToDesk( wct: WindowContainerTransaction, Loading Loading @@ -275,7 +320,13 @@ class RootTaskDesksOrganizer( // Appearing root matches desk request. val deskId = taskInfo.taskId logV("Desk #$deskId appeared") deskRootsByDeskId[deskId] = DeskRoot(deskId, taskInfo, leash) deskRootsByDeskId[deskId] = DeskRoot( deskId = deskId, taskInfo = taskInfo, leash = leash, users = mutableSetOf(deskRequest.userId), ) createDeskRootRequests.remove(deskRequest) deskRequest.onCreateCallback.onCreated(deskId) createDeskMinimizationRoot(displayId = appearingInDisplayId, deskId = deskId) Loading Loading @@ -430,6 +481,8 @@ class RootTaskDesksOrganizer( val taskInfo: RunningTaskInfo, val leash: SurfaceControl, val children: MutableSet<Int> = mutableSetOf(), val users: MutableSet<Int> = mutableSetOf(), var isLaunchRootRequested: Boolean = false, ) { val token: WindowContainerToken = taskInfo.token } Loading @@ -449,15 +502,24 @@ class RootTaskDesksOrganizer( private data class CreateDeskRequest( val displayId: Int, val userId: Int, val onCreateCallback: OnCreateCallback, ) private data class CreateDeskMinimizationRootRequest(val displayId: Int, val deskId: Int) private fun logD(msg: String, vararg arguments: Any?) { ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } private fun logW(msg: String, vararg arguments: Any?) { 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) } Loading @@ -473,7 +535,9 @@ class RootTaskDesksOrganizer( val minimizationRoot = deskMinimizationRootsByDeskId[deskId] pw.println("$innerPrefix #$deskId visible=${root.taskInfo.isVisible}") pw.println("$innerPrefix displayId=${root.taskInfo.displayId}") pw.println("$innerPrefix isLaunchRootRequested=${root.isLaunchRootRequested}") pw.println("$innerPrefix children=${root.children}") pw.println("$innerPrefix users=${root.users}") pw.println("$innerPrefix minimization root:") pw.println("$innerPrefix rootId=${minimizationRoot?.rootId}") if (minimizationRoot != null) { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt +8 −4 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.mockito.quality.Strictness Loading Loading @@ -213,12 +214,15 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun testUserChanged_createsDeskWhenNeeded() = testScope.runTest { val userId = 11 whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) val userChangeListenerCaptor = argumentCaptor<UserChangeListener>() verify(mockShellController).addUserChangeListener(userChangeListenerCaptor.capture()) whenever(mockDesktopRepository.getNumberOfDesks(displayId = 2)).thenReturn(0) whenever(mockDesktopRepository.getNumberOfDesks(displayId = 3)).thenReturn(0) whenever(mockDesktopRepository.getNumberOfDesks(displayId = 4)).thenReturn(1) val mockRepository = mock<DesktopRepository>() whenever(mockDesktopUserRepositories.getProfile(userId)).thenReturn(mockRepository) whenever(mockRepository.getNumberOfDesks(displayId = 2)).thenReturn(0) whenever(mockRepository.getNumberOfDesks(displayId = 3)).thenReturn(0) whenever(mockRepository.getNumberOfDesks(displayId = 4)).thenReturn(1) whenever(mockRootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(2, 3, 4)) desktopRepositoryInitializer.initialize(mockDesktopUserRepositories) handler.onDisplayAdded(displayId = 2) Loading @@ -227,7 +231,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { runCurrent() clearInvocations(mockDesktopTasksController) userChangeListenerCaptor.lastValue.onUserChanged(1, context) userChangeListenerCaptor.lastValue.onUserChanged(userId, context) runCurrent() verify(mockDesktopTasksController).createDesk(displayId = 2) Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt +9 −13 Original line number Diff line number Diff line Loading @@ -49,9 +49,6 @@ class DesktopDisplayEventHandler( private val desktopDisplayModeController: DesktopDisplayModeController, ) : OnDisplaysChangedListener, OnDeskRemovedListener { private val desktopRepository: DesktopRepository get() = desktopUserRepositories.current init { shellInit.addInitCallback({ onInit() }, this) } Loading @@ -66,7 +63,7 @@ class DesktopDisplayEventHandler( object : UserChangeListener { override fun onUserChanged(newUserId: Int, userContext: Context) { val displayIds = rootTaskDisplayAreaOrganizer.displayIds createDefaultDesksIfNeeded(displayIds.toSet()) createDefaultDesksIfNeeded(displayIds.toSet(), newUserId) } } ) Loading @@ -78,7 +75,7 @@ class DesktopDisplayEventHandler( desktopDisplayModeController.refreshDisplayWindowingMode() } createDefaultDesksIfNeeded(displayIds = setOf(displayId)) createDefaultDesksIfNeeded(displayIds = setOf(displayId), userId = null) } override fun onDisplayRemoved(displayId: Int) { Loading @@ -99,23 +96,22 @@ class DesktopDisplayEventHandler( } override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) { val remainingDesks = desktopRepository.getNumberOfDesks(lastDisplayId) if (remainingDesks == 0) { logV("All desks removed from display#$lastDisplayId") createDefaultDesksIfNeeded(setOf(lastDisplayId)) } createDefaultDesksIfNeeded(setOf(lastDisplayId), userId = null) } private fun createDefaultDesksIfNeeded(displayIds: Set<Int>) { private fun createDefaultDesksIfNeeded(displayIds: Set<Int>, userId: Int?) { if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return logV("createDefaultDesksIfNeeded displays=%s", displayIds) mainScope.launch { desktopRepositoryInitializer.isInitialized.collect { initialized -> if (!initialized) return@collect val repository = userId?.let { desktopUserRepositories.getProfile(userId) } ?: desktopUserRepositories.current displayIds .filter { displayId -> displayId != Display.INVALID_DISPLAY } .filter { displayId -> supportsDesks(displayId) } .filter { displayId -> desktopRepository.getNumberOfDesks(displayId) == 0 } .filter { displayId -> repository.getNumberOfDesks(displayId) == 0 } .also { displaysNeedingDesk -> logV( "createDefaultDesksIfNeeded creating default desks in displays=%s", Loading @@ -125,7 +121,7 @@ class DesktopDisplayEventHandler( .forEach { displayId -> // TODO: b/393978539 - consider activating the desk on creation when // applicable, such as for connected displays. desktopTasksController.createDesk(displayId) desktopTasksController.createDesk(displayId, repository.userId) } cancel() } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +55 −28 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import android.os.Handler import android.os.IBinder import android.os.SystemProperties import android.os.UserHandle import android.os.UserManager import android.util.Slog import android.view.Display import android.view.Display.DEFAULT_DISPLAY Loading Loading @@ -115,7 +116,6 @@ import com.android.wm.shell.desktopmode.multidesks.DeskTransition import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener import com.android.wm.shell.desktopmode.multidesks.createDesk import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer.DeskRecreationFactory import com.android.wm.shell.draganddrop.DragAndDropController Loading Loading @@ -163,6 +163,7 @@ import java.util.Optional import java.util.concurrent.Executor import java.util.concurrent.TimeUnit import java.util.function.Consumer import kotlin.coroutines.suspendCoroutine import kotlin.jvm.optionals.getOrNull /** Loading Loading @@ -283,14 +284,8 @@ class DesktopTasksController( if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desktopRepositoryInitializer.deskRecreationFactory = DeskRecreationFactory { deskUserId, destinationDisplayId, deskId -> if (deskUserId != userId) { // TODO: b/400984250 - add multi-user support for multi-desk restoration. logW("Tried to re-create desk of another user.") null } else { desksOrganizer.createDesk(destinationDisplayId) } DeskRecreationFactory { deskUserId, destinationDisplayId, _ -> createDeskSuspending(displayId = destinationDisplayId, userId = deskUserId) } } } Loading Loading @@ -493,20 +488,53 @@ class DesktopTasksController( runOnTransitStart?.invoke(transition) } /** Creates a new desk in the given display. */ fun createDesk(displayId: Int) { /** Adds a new desk to the given display for the given user. */ fun createDesk(displayId: Int, userId: Int = this.userId) { logV("addDesk displayId=%d, userId=%d", displayId, userId) val repository = userRepositories.getProfile(userId) createDesk(displayId, userId) { deskId -> if (deskId == null) { logW("Failed to add desk in displayId=%d for userId=%d", displayId, userId) } else { repository.addDesk(displayId = displayId, deskId = deskId) } } } private fun createDesk(displayId: Int, userId: Int = this.userId, onResult: (Int?) -> Unit) { if (displayId == Display.INVALID_DISPLAY) { logW("createDesk attempt with invalid displayId", displayId) onResult(null) return } if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desksOrganizer.createDesk(displayId) { deskId -> taskRepository.addDesk(displayId = displayId, deskId = deskId) } } else { if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { // In single-desk, the desk reuses the display id. taskRepository.addDesk(displayId = displayId, deskId = displayId) logD("createDesk reusing displayId=%d for single-desk", displayId) onResult(displayId) return } if ( DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_HSUM.isTrue && UserManager.isHeadlessSystemUserMode() && UserHandle.USER_SYSTEM == userId ) { logW("createDesk ignoring attempt for system user") return } desksOrganizer.createDesk(displayId, userId) { deskId -> logD( "createDesk obtained deskId=%d for displayId=%d and userId=%d", deskId, displayId, userId, ) onResult(deskId) } } private suspend fun createDeskSuspending(displayId: Int, userId: Int = this.userId): Int? = suspendCoroutine { cont -> createDesk(displayId, userId) { deskId -> cont.resumeWith(Result.success(deskId)) } } /** Moves task to desktop mode if task is running, else launches it in desktop mode. */ Loading Loading @@ -3024,8 +3052,8 @@ class DesktopTasksController( } val wct = WindowContainerTransaction() if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { tasksToRemove.forEach { // TODO: b/404595635 - consider moving this block into [DesksOrganizer]. val task = shellTaskOrganizer.getRunningTaskInfo(it) if (task != null) { wct.removeTask(task.token) Loading @@ -3033,9 +3061,8 @@ class DesktopTasksController( recentTasksController?.removeBackgroundTask(it) } } } else { // TODO: 362720497 - double check background tasks are also removed. desksOrganizer.removeDesk(wct, deskId) if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desksOrganizer.removeDesk(wct, deskId, userId) } if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue && wct.isEmpty) return val transition = transitions.startTransition(TRANSIT_CLOSE, wct, /* handler= */ null) Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt +4 −12 Original line number Diff line number Diff line Loading @@ -18,13 +18,11 @@ package com.android.wm.shell.desktopmode.multidesks import android.app.ActivityManager import android.window.TransitionInfo import android.window.WindowContainerTransaction import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer.OnCreateCallback import kotlin.coroutines.suspendCoroutine /** An organizer of desk containers in which to host child desktop windows. */ interface DesksOrganizer { /** Creates a new desk container in the given display. */ fun createDesk(displayId: Int, callback: OnCreateCallback) /** Creates a new desk container to use in the given display for the given user. */ fun createDesk(displayId: Int, userId: Int, callback: OnCreateCallback) /** Activates the given desk, making it visible in its display. */ fun activateDesk(wct: WindowContainerTransaction, deskId: Int) Loading @@ -32,8 +30,8 @@ interface DesksOrganizer { /** Deactivates the given desk, removing it as the default launch container for new tasks. */ fun deactivateDesk(wct: WindowContainerTransaction, deskId: Int) /** Removes the given desk and its desktop windows. */ fun removeDesk(wct: WindowContainerTransaction, deskId: Int) /** Removes the given desk of the given user. */ fun removeDesk(wct: WindowContainerTransaction, deskId: Int, userId: Int) /** Moves the given task to the given desk. */ fun moveTaskToDesk( Loading Loading @@ -87,9 +85,3 @@ interface DesksOrganizer { fun onCreated(deskId: Int) } } /** Creates a new desk container in the given display. */ suspend fun DesksOrganizer.createDesk(displayId: Int): Int = suspendCoroutine { cont -> val onCreateCallback = OnCreateCallback { deskId -> cont.resumeWith(Result.success(deskId)) } createDesk(displayId, onCreateCallback) }
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt +83 −19 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.window.TransitionInfo import android.window.WindowContainerToken import android.window.WindowContainerTransaction import androidx.core.util.forEach import androidx.core.util.valueIterator import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer Loading @@ -40,7 +41,13 @@ import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellInit import java.io.PrintWriter /** A [DesksOrganizer] that uses root tasks as the container of each desk. */ /** * A [DesksOrganizer] that uses root tasks as the container of each desk. * * Note that root tasks are reusable between multiple users at the same time, and may also be * pre-created to have one ready for the first entry to the default desk, so root-task existence * does not imply a formal desk exists to the user. */ class RootTaskDesksOrganizer( shellInit: ShellInit, shellCommandHandler: ShellCommandHandler, Loading @@ -65,9 +72,26 @@ class RootTaskDesksOrganizer( } } override fun createDesk(displayId: Int, callback: OnCreateCallback) { logV("createDesk in display: %d", displayId) createDeskRootRequests += CreateDeskRequest(displayId, callback) 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 } .firstOrNull() if (unassignedDesk != null) { unassignedDesk.users.add(userId) callback.onCreated(unassignedDesk.deskId) return } createDeskRoot(displayId, userId, callback) } 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( displayId, WINDOWING_MODE_FREEFORM, Loading @@ -76,32 +100,53 @@ class RootTaskDesksOrganizer( ) } override fun removeDesk(wct: WindowContainerTransaction, deskId: Int) { logV("removeDesk %d", deskId) deskRootsByDeskId[deskId]?.let { root -> wct.removeRootTask(root.token) } override fun removeDesk(wct: WindowContainerTransaction, deskId: Int, userId: Int) { logV("removeDesk %d for userId=%d", deskId, userId) val deskRoot = deskRootsByDeskId[deskId] if (deskRoot == null) { logW("removeDesk attempted to remove non-existent desk=%d", deskId) return } updateLaunchRoot(wct, deskId, enabled = false) deskRoot.users.remove(userId) if (deskRoot.users.isEmpty()) { // No longer in use by any users, remove it completely. logD("removeDesk %d is no longer used by any users, removing it completely", deskId) wct.removeRootTask(deskRoot.token) deskMinimizationRootsByDeskId[deskId]?.let { root -> wct.removeRootTask(root.token) } } } override fun activateDesk(wct: WindowContainerTransaction, deskId: Int) { logV("activateDesk %d", deskId) val root = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" } wct.reorder(root.token, /* onTop= */ true) wct.setLaunchRoot( /* container= */ root.taskInfo.token, /* windowingModes= */ intArrayOf(WINDOWING_MODE_FREEFORM, WINDOWING_MODE_UNDEFINED), /* activityTypes= */ intArrayOf(ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD), ) updateLaunchRoot(wct, deskId, enabled = true) } override fun deactivateDesk(wct: WindowContainerTransaction, deskId: Int) { logV("deactivateDesk %d", deskId) updateLaunchRoot(wct, deskId, enabled = false) } private fun updateLaunchRoot(wct: WindowContainerTransaction, deskId: Int, enabled: Boolean) { val root = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" } root.isLaunchRootRequested = enabled logD("updateLaunchRoot deskId=%d enabled=%b", deskId, enabled) if (enabled) { wct.setLaunchRoot( /* container= */ root.taskInfo.token, /* windowingModes= */ intArrayOf(WINDOWING_MODE_FREEFORM, WINDOWING_MODE_UNDEFINED), /* activityTypes= */ intArrayOf(ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD), ) } else { wct.setLaunchRoot( /* container= */ root.taskInfo.token, /* windowingModes= */ null, /* activityTypes= */ null, ) } } override fun moveTaskToDesk( wct: WindowContainerTransaction, Loading Loading @@ -275,7 +320,13 @@ class RootTaskDesksOrganizer( // Appearing root matches desk request. val deskId = taskInfo.taskId logV("Desk #$deskId appeared") deskRootsByDeskId[deskId] = DeskRoot(deskId, taskInfo, leash) deskRootsByDeskId[deskId] = DeskRoot( deskId = deskId, taskInfo = taskInfo, leash = leash, users = mutableSetOf(deskRequest.userId), ) createDeskRootRequests.remove(deskRequest) deskRequest.onCreateCallback.onCreated(deskId) createDeskMinimizationRoot(displayId = appearingInDisplayId, deskId = deskId) Loading Loading @@ -430,6 +481,8 @@ class RootTaskDesksOrganizer( val taskInfo: RunningTaskInfo, val leash: SurfaceControl, val children: MutableSet<Int> = mutableSetOf(), val users: MutableSet<Int> = mutableSetOf(), var isLaunchRootRequested: Boolean = false, ) { val token: WindowContainerToken = taskInfo.token } Loading @@ -449,15 +502,24 @@ class RootTaskDesksOrganizer( private data class CreateDeskRequest( val displayId: Int, val userId: Int, val onCreateCallback: OnCreateCallback, ) private data class CreateDeskMinimizationRootRequest(val displayId: Int, val deskId: Int) private fun logD(msg: String, vararg arguments: Any?) { ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } private fun logW(msg: String, vararg arguments: Any?) { 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) } Loading @@ -473,7 +535,9 @@ class RootTaskDesksOrganizer( val minimizationRoot = deskMinimizationRootsByDeskId[deskId] pw.println("$innerPrefix #$deskId visible=${root.taskInfo.isVisible}") pw.println("$innerPrefix displayId=${root.taskInfo.displayId}") pw.println("$innerPrefix isLaunchRootRequested=${root.isLaunchRootRequested}") pw.println("$innerPrefix children=${root.children}") pw.println("$innerPrefix users=${root.users}") pw.println("$innerPrefix minimization root:") pw.println("$innerPrefix rootId=${minimizationRoot?.rootId}") if (minimizationRoot != null) { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt +8 −4 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.mockito.quality.Strictness Loading Loading @@ -213,12 +214,15 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun testUserChanged_createsDeskWhenNeeded() = testScope.runTest { val userId = 11 whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) val userChangeListenerCaptor = argumentCaptor<UserChangeListener>() verify(mockShellController).addUserChangeListener(userChangeListenerCaptor.capture()) whenever(mockDesktopRepository.getNumberOfDesks(displayId = 2)).thenReturn(0) whenever(mockDesktopRepository.getNumberOfDesks(displayId = 3)).thenReturn(0) whenever(mockDesktopRepository.getNumberOfDesks(displayId = 4)).thenReturn(1) val mockRepository = mock<DesktopRepository>() whenever(mockDesktopUserRepositories.getProfile(userId)).thenReturn(mockRepository) whenever(mockRepository.getNumberOfDesks(displayId = 2)).thenReturn(0) whenever(mockRepository.getNumberOfDesks(displayId = 3)).thenReturn(0) whenever(mockRepository.getNumberOfDesks(displayId = 4)).thenReturn(1) whenever(mockRootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(2, 3, 4)) desktopRepositoryInitializer.initialize(mockDesktopUserRepositories) handler.onDisplayAdded(displayId = 2) Loading @@ -227,7 +231,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { runCurrent() clearInvocations(mockDesktopTasksController) userChangeListenerCaptor.lastValue.onUserChanged(1, context) userChangeListenerCaptor.lastValue.onUserChanged(userId, context) runCurrent() verify(mockDesktopTasksController).createDesk(displayId = 2) Loading