From c0add4e1010ec2e652de03aa169a6cff5fd7f9ee Mon Sep 17 00:00:00 2001 From: Jorge Gil Date: Mon, 24 Feb 2025 17:12:19 +0000 Subject: [39/N] Desks: Restore persisted desks on reboot Adds support for restoring persisted desks on shell init. Restoring is done as follows: 1) DisplayController init must run before DesktopRepositoryInitializerImpl to have a list of available displays ready (already runs first because of dagger dependencies) 2) On init, DesktopRepositoryInitializerImpl selects desks to persist based on available displays from (1) and desk id validity for single-desk edge cases 3) For each desk, DesktopRepositoryInitializerImpl creates a new desk instance (root task), inserts the desktop data with the new deskId and deletes the data under the old deskId 4) DesktopRepositoryInitializerImpl maintains an "isInitialized" StateFlow and pings it when finished 5) DesktopDisplayEventHandler collects this StateFlow, and when |true|, checks whether an empty desk needs to be created. This prevents creating the empty/default desk when persisted desks will be restored. It also starts non-running tasks when activating a desk similar to how bringDesktopAppsToFront() did it for single-desk setups. Flag: com.android.window.flags.enable_multiple_desktops_backend Bug: 393961770 Bug: 391484873 Fix: 393978864 Test: create a couple of desks and add tasks to them, reboot - verify desks still exist in the repository (with new deskIds) Change-Id: Idf38fef305088043ea4dbb0a8095181248b6116d --- .../com/android/wm/shell/dagger/WMShellModule.java | 8 +- .../desktopmode/DesktopDisplayEventHandler.kt | 28 ++- .../wm/shell/desktopmode/DesktopRepository.kt | 26 ++- .../wm/shell/desktopmode/DesktopTasksController.kt | 50 +++-- .../shell/desktopmode/multidesks/DesksOrganizer.kt | 8 + .../persistence/DesktopPersistentRepository.kt | 32 +++- .../persistence/DesktopRepositoryInitializer.kt | 16 +- .../DesktopRepositoryInitializerImpl.kt | 174 ++++++++++++----- .../desktopmode/DesktopDisplayEventHandlerTest.kt | 72 ++++++- .../wm/shell/desktopmode/DesktopRepositoryTest.kt | 11 ++ .../desktopmode/DesktopTasksControllerTest.kt | 50 +++++ .../multidesks/RootTaskDesksOrganizerTest.kt | 210 +++++++++------------ .../DesktopRepositoryInitializerTest.kt | 123 +++++++++--- 13 files changed, 574 insertions(+), 234 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index fa960367cf60..2987b2e492f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -757,6 +757,7 @@ public abstract class WMShellModule { ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, DragToDesktopTransitionHandler dragToDesktopTransitionHandler, @DynamicOverride DesktopUserRepositories desktopUserRepositories, + DesktopRepositoryInitializer desktopRepositoryInitializer, Optional desktopImmersiveController, DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver, LaunchAdjacentController launchAdjacentController, @@ -802,6 +803,7 @@ public abstract class WMShellModule { dragToDesktopTransitionHandler, desktopImmersiveController.get(), desktopUserRepositories, + desktopRepositoryInitializer, recentsTransitionHandler, multiInstanceHelper, mainExecutor, @@ -1309,10 +1311,12 @@ public abstract class WMShellModule { static Optional provideDesktopDisplayEventHandler( Context context, ShellInit shellInit, + @ShellMainThread CoroutineScope mainScope, DisplayController displayController, Optional desktopUserRepositories, Optional desktopTasksController, - Optional desktopDisplayModeController + Optional desktopDisplayModeController, + DesktopRepositoryInitializer desktopRepositoryInitializer ) { if (!DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.empty(); @@ -1321,7 +1325,9 @@ public abstract class WMShellModule { new DesktopDisplayEventHandler( context, shellInit, + mainScope, displayController, + desktopRepositoryInitializer, desktopUserRepositories.get(), desktopTasksController.get(), desktopDisplayModeController.get())); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt index afc48acad4f5..683b74392fa6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt @@ -23,15 +23,21 @@ import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener +import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch /** Handles display events in desktop mode */ class DesktopDisplayEventHandler( private val context: Context, shellInit: ShellInit, + private val mainScope: CoroutineScope, private val displayController: DisplayController, + private val desktopRepositoryInitializer: DesktopRepositoryInitializer, private val desktopUserRepositories: DesktopUserRepositories, private val desktopTasksController: DesktopTasksController, private val desktopDisplayModeController: DesktopDisplayModeController, @@ -61,15 +67,19 @@ class DesktopDisplayEventHandler( logV("Display #$displayId does not support desks") return } - logV("Creating new desk in new display#$displayId") - // TODO: b/362720497 - when SystemUI crashes with a freeform task open for any reason, the - // task is recreated and received in [FreeformTaskListener] before this display callback - // is invoked, which results in the repository trying to add the task to a desk before the - // desk has been recreated here, which may result in a crash-loop if the repository is - // checking that the desk exists before adding a task to it. See b/391984373. - desktopTasksController.createDesk(displayId) - // TODO: b/393978539 - consider activating the desk on creation when applicable, such as - // for connected displays. + + mainScope.launch { + desktopRepositoryInitializer.isInitialized.collect { initialized -> + if (!initialized) return@collect + if (desktopRepository.getNumberOfDesks(displayId) == 0) { + logV("Creating new desk in new display#$displayId") + // TODO: b/393978539 - consider activating the desk on creation when + // applicable, such as for connected displays. + desktopTasksController.createDesk(displayId) + } + cancel() + } + } } override fun onDisplayRemoved(displayId: Int) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index f64bd757de3b..4e20c292445d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -818,7 +818,6 @@ class DesktopRepository( } /** Minimizes the task in its desk. */ - @VisibleForTesting fun minimizeTaskInDesk(displayId: Int, deskId: Int, taskId: Int) { logD("MinimizeTaskInDesk: displayId=%d deskId=%d, task=%d", displayId, deskId, taskId) desktopData.getDesk(deskId)?.minimizedTasks?.add(taskId) @@ -933,6 +932,12 @@ class DesktopRepository( listener.onDeskRemoved(displayId = desk.displayId, deskId = desk.deskId) } } + if ( + DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue && + DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue + ) { + removeDeskFromPersistentRepository(desk) + } return activeTasks } @@ -1031,6 +1036,24 @@ class DesktopRepository( } } + private fun removeDeskFromPersistentRepository(desk: Desk) { + mainCoroutineScope.launch { + try { + logD( + "updatePersistentRepositoryForRemovedDesk user=%d desk=%d", + userId, + desk.deskId, + ) + persistentRepository.removeDesktop(userId = userId, desktopId = desk.deskId) + } catch (throwable: Throwable) { + logE( + "An exception occurred while updating the persistent repository \n%s", + throwable.stackTrace, + ) + } + } + } + internal fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("${prefix}DesktopRepository") @@ -1049,6 +1072,7 @@ class DesktopRepository( } .forEach { (displayId, activeDeskId, desks) -> pw.println("${prefix}Display #$displayId:") + pw.println("${innerPrefix}numOfDesks=${desks.size}") pw.println("${innerPrefix}activeDesk=$activeDeskId") pw.println("${innerPrefix}desks:") val desksPrefix = "$innerPrefix " diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index f67323bb7eb1..6776b76f0d5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -112,6 +112,9 @@ 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 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE @@ -191,6 +194,7 @@ class DesktopTasksController( private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler, private val desktopImmersiveController: DesktopImmersiveController, private val userRepositories: DesktopUserRepositories, + desktopRepositoryInitializer: DesktopRepositoryInitializer, private val recentsTransitionHandler: RecentsTransitionHandler, private val multiInstanceHelper: MultiInstanceHelper, @ShellMainThread private val mainExecutor: ShellExecutor, @@ -268,6 +272,19 @@ class DesktopTasksController( } userId = ActivityManager.getCurrentUser() taskRepository = userRepositories.getProfile(userId) + + 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 recreated desk of another user.") + deskId + } else { + desksOrganizer.createDesk(destinationDisplayId) + } + } + } } private fun onInit() { @@ -1689,15 +1706,7 @@ class DesktopTasksController( wct.reorder(runningTaskInfo.token, /* onTop= */ true) } else if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { // Task is not running, start it - wct.startTask( - taskId, - ActivityOptions.makeBasic() - .apply { - launchWindowingMode = WINDOWING_MODE_FREEFORM - splashScreenStyle = SPLASH_SCREEN_STYLE_ICON - } - .toBundle(), - ) + wct.startTask(taskId, createActivityOptionsForStartTask().toBundle()) } } @@ -2805,9 +2814,6 @@ class DesktopTasksController( } prepareForDeskActivation(displayId, wct) desksOrganizer.activateDesk(wct, deskId) - if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { - // TODO: 362720497 - do non-running tasks need to be restarted with |wct#startTask|? - } taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( doesAnyTaskRequireTaskbarRounding(displayId) ) @@ -2825,6 +2831,19 @@ class DesktopTasksController( desksOrganizer.minimizeTask(wct, deskId, taskToMinimize) } } + if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue) { + expandedTasksOrderedFrontToBack + .filter { taskId -> taskId != taskIdToMinimize } + .reversed() + .forEach { taskId -> + val runningTaskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId) + if (runningTaskInfo == null) { + wct.startTask(taskId, createActivityOptionsForStartTask().toBundle()) + } else { + desksOrganizer.reorderTaskToFront(wct, deskId, runningTaskInfo) + } + } + } return { transition -> val activateDeskTransition = if (newTaskIdInFront != null) { @@ -3476,6 +3495,13 @@ class DesktopTasksController( } } + private fun createActivityOptionsForStartTask(): ActivityOptions { + return ActivityOptions.makeBasic().apply { + launchWindowingMode = WINDOWING_MODE_FREEFORM + splashScreenStyle = SPLASH_SCREEN_STYLE_ICON + } + } + private fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("${prefix}DesktopTasksController") diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt index 8c4bc2598dff..5a988fcd1b77 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt @@ -18,6 +18,8 @@ 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 { @@ -82,3 +84,9 @@ 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) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt index 1566544f5303..f71eacab518d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt @@ -132,10 +132,7 @@ class DesktopPersistentRepository(private val dataStore: DataStore + val currentRepository = + persistentRepositories.getDesktopRepoByUserOrDefault( + userId, + DesktopRepositoryState.getDefaultInstance(), + ) + persistentRepositories + .toBuilder() + .putDesktopRepoByUser( + userId, + currentRepository.toBuilder().removeDesktop(desktopId).build(), + ) + .build() + } + } catch (throwable: Throwable) { + Log.e( + TAG, + "Error in removing desktop related data, data is " + + "stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE", + throwable, + ) + } + } + suspend fun removeUsers(uids: List) { try { dataStore.updateData { persistentRepositories: DesktopPersistentRepositories -> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt index a26ebbf4c99a..8191181cac11 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt @@ -17,8 +17,22 @@ package com.android.wm.shell.desktopmode.persistence import com.android.wm.shell.desktopmode.DesktopUserRepositories +import kotlinx.coroutines.flow.StateFlow /** Interface for initializing the [DesktopUserRepositories]. */ -fun interface DesktopRepositoryInitializer { +interface DesktopRepositoryInitializer { + /** A factory used to recreate a desk from persistence. */ + var deskRecreationFactory: DeskRecreationFactory + + /** A flow that emits true when the repository has been initialized. */ + val isInitialized: StateFlow + + /** Initialize the user repositories from a persistent data store. */ fun initialize(userRepositories: DesktopUserRepositories) + + /** A factory for recreating desks. */ + fun interface DeskRecreationFactory { + /** Recreates a restored desk and returns the new desk id. */ + suspend fun recreateDesk(userId: Int, destinationDisplayId: Int, deskId: Int): Int + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt index 0507e59c06e1..49cb7391fe97 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt @@ -17,13 +17,19 @@ package com.android.wm.shell.desktopmode.persistence import android.content.Context +import android.view.Display import android.window.DesktopExperienceFlags import android.window.DesktopModeFlags +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopUserRepositories +import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer.DeskRecreationFactory +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch /** @@ -37,62 +43,136 @@ class DesktopRepositoryInitializerImpl( private val persistentRepository: DesktopPersistentRepository, @ShellMainThread private val mainCoroutineScope: CoroutineScope, ) : DesktopRepositoryInitializer { + + override var deskRecreationFactory: DeskRecreationFactory = DefaultDeskRecreationFactory() + + private val _isInitialized = MutableStateFlow(false) + override val isInitialized: StateFlow = _isInitialized + override fun initialize(userRepositories: DesktopUserRepositories) { - if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) return + if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue) { + _isInitialized.value = true + return + } // TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized mainCoroutineScope.launch { - val desktopUserPersistentRepositoryMap = - persistentRepository.getUserDesktopRepositoryMap() ?: return@launch - for (userId in desktopUserPersistentRepositoryMap.keys) { - val repository = userRepositories.getProfile(userId) - val desktopRepositoryState = - persistentRepository.getDesktopRepositoryState(userId) ?: continue - val desktopByDesktopIdMap = desktopRepositoryState.desktopMap - for (desktopId in desktopByDesktopIdMap.keys) { - val persistentDesktop = - persistentRepository.readDesktop(userId, desktopId) ?: continue - val maxTasks = - DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 } - ?: persistentDesktop.zOrderedTasksCount - var visibleTasksCount = 0 - repository.addDesk( - displayId = persistentDesktop.displayId, - deskId = - if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { - persistentDesktop.desktopId - } else { - // When disabled, desk ids are always the display id. - persistentDesktop.displayId - }, + try { + val desktopUserPersistentRepositoryMap = + persistentRepository.getUserDesktopRepositoryMap() ?: return@launch + for (userId in desktopUserPersistentRepositoryMap.keys) { + val repository = userRepositories.getProfile(userId) + val desktopRepositoryState = + persistentRepository.getDesktopRepositoryState(userId) ?: continue + val desksToRestore = getDesksToRestore(desktopRepositoryState, userId) + logV( + "initialize() will restore desks=%s user=%d", + desksToRestore.map { it.desktopId }, + userId, ) - persistentDesktop.zOrderedTasksList - // Reverse it so we initialize the repo from bottom to top. - .reversed() - .mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] } - // TODO: b/362720497 - add tasks to their respective desk when multi-desk - // persistence is implemented. - .forEach { task -> - if ( - task.desktopTaskState == DesktopTaskState.VISIBLE && - visibleTasksCount < maxTasks - ) { - visibleTasksCount++ - repository.addTask( - persistentDesktop.displayId, - task.taskId, - isVisible = false, - ) - } else { - repository.addTask( - persistentDesktop.displayId, - task.taskId, + desksToRestore.forEach { persistentDesktop -> + val maxTasks = getTaskLimit(persistentDesktop) + val displayId = persistentDesktop.displayId + val deskId = persistentDesktop.desktopId + // TODO: b/401107440 - Implement desk restoration to other displays. + val newDisplayId = Display.DEFAULT_DISPLAY + val newDeskId = + deskRecreationFactory.recreateDesk( + userId = userId, + destinationDisplayId = newDisplayId, + deskId = deskId, + ) + logV( + "Recreated desk=%d in display=%d using new deskId=%d and displayId=%d", + deskId, + displayId, + newDeskId, + newDisplayId, + ) + if (newDeskId != deskId || newDisplayId != displayId) { + logV("Removing obsolete desk from persistence under deskId=%d", deskId) + persistentRepository.removeDesktop(userId, deskId) + } + + // TODO: b/393961770 - [DesktopRepository] doesn't save desks to the + // persistent repository until a task is added to them. Update it so that + // empty desks can be restored too. + repository.addDesk(displayId = displayId, deskId = newDeskId) + var visibleTasksCount = 0 + persistentDesktop.zOrderedTasksList + // Reverse it so we initialize the repo from bottom to top. + .reversed() + .mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] } + .forEach { task -> + // Visible here means non-minimized a.k.a. expanded, it does not + // mean + // it is visible in WM (and |DesktopRepository|) terms. + val isVisible = + task.desktopTaskState == DesktopTaskState.VISIBLE && + visibleTasksCount < maxTasks + + repository.addTaskToDesk( + displayId = displayId, + deskId = newDeskId, + taskId = task.taskId, isVisible = false, ) - repository.minimizeTask(persistentDesktop.displayId, task.taskId) + + if (isVisible) { + visibleTasksCount++ + } else { + repository.minimizeTaskInDesk( + displayId = displayId, + deskId = newDeskId, + taskId = task.taskId, + ) + } } - } + } } + } finally { + _isInitialized.value = true } } } + + private suspend fun getDesksToRestore( + state: DesktopRepositoryState, + userId: Int, + ): Set { + // TODO: b/365873835 - what about desks that won't be restored? + // - invalid desk ids from multi-desk -> single-desk switching can be ignored / deleted. + val limitToSingleDeskPerDisplay = + !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue + return state.desktopMap.keys + .mapNotNull { deskId -> + persistentRepository.readDesktop(userId, deskId)?.takeIf { desk -> + // Do not restore invalid desks when multi-desks is disabled. This is + // possible if the feature is disabled after having created multiple desks. + val isValidSingleDesk = desk.desktopId == desk.displayId + (!limitToSingleDeskPerDisplay || isValidSingleDesk) + } + } + .toSet() + } + + private fun getTaskLimit(persistedDesk: Desktop): Int = + DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 } + ?: persistedDesk.zOrderedTasksCount + + private fun logV(msg: String, vararg arguments: Any?) { + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + /** A default implementation of [DeskRecreationFactory] that reuses the desk id. */ + private class DefaultDeskRecreationFactory : DeskRecreationFactory { + override suspend fun recreateDesk( + userId: Int, + destinationDisplayId: Int, + deskId: Int, + ): Int = deskId + } + + companion object { + private const val TAG = "DesktopRepositoryInitializerImpl" + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt index 8ad54f5a0bb4..275d7b73a112 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt @@ -28,14 +28,22 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.spy +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.whenever @@ -46,15 +54,18 @@ import org.mockito.quality.Strictness * * Usage: atest WMShellUnitTests:DesktopDisplayEventHandlerTest */ +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidTestingRunner::class) class DesktopDisplayEventHandlerTest : ShellTestCase() { @Mock lateinit var testExecutor: ShellExecutor @Mock lateinit var displayController: DisplayController @Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories + @Mock private lateinit var mockDesktopRepositoryInitializer: DesktopRepositoryInitializer @Mock private lateinit var mockDesktopRepository: DesktopRepository @Mock private lateinit var mockDesktopTasksController: DesktopTasksController @Mock private lateinit var desktopDisplayModeController: DesktopDisplayModeController + private val testScope = TestScope() private lateinit var mockitoSession: StaticMockitoSession private lateinit var shellInit: ShellInit @@ -77,7 +88,9 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { DesktopDisplayEventHandler( context, shellInit, + testScope.backgroundScope, displayController, + mockDesktopRepositoryInitializer, mockDesktopUserRepositories, mockDesktopTasksController, desktopDisplayModeController, @@ -89,17 +102,66 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { @After fun tearDown() { + testScope.cancel() mockitoSession.finishMocking() } @Test - fun testDisplayAdded_supportsDesks_createsDesk() { - whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) + fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_createsDesk() = + testScope.runTest { + val stateFlow = MutableStateFlow(false) + whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow) + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) - onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) + onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) + stateFlow.emit(true) + runCurrent() - verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY) - } + verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY) + } + + @Test + fun testDisplayAdded_supportsDesks_desktopRepositoryNotInitialized_doesNotCreateDesk() = + testScope.runTest { + val stateFlow = MutableStateFlow(false) + whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow) + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) + + onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) + runCurrent() + + verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY) + } + + @Test + fun testDisplayAdded_supportsDesks_desktopRepositoryInitializedTwice_createsDeskOnce() = + testScope.runTest { + val stateFlow = MutableStateFlow(false) + whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow) + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) + + onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) + stateFlow.emit(true) + stateFlow.emit(true) + runCurrent() + + verify(mockDesktopTasksController, times(1)).createDesk(DEFAULT_DISPLAY) + } + + @Test + fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_deskExists_doesNotCreateDesk() = + testScope.runTest { + val stateFlow = MutableStateFlow(false) + whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow) + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) + whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(1) + + onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) + stateFlow.emit(true) + runCurrent() + + verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY) + } @Test fun testDisplayAdded_cannotEnterDesktopMode_doesNotCreateDesk() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index de92d391645a..f84a1a38bdfc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -1202,6 +1202,17 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(2) } + @Test + @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) + fun removeDesk_removesFromPersistence() = + runTest(StandardTestDispatcher()) { + repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2) + + repo.removeDesk(deskId = 2) + + verify(persistentRepository).removeDesktop(DEFAULT_USER_ID, 2) + } + @Test fun getTaskInFullImmersiveState_byDisplay() { repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 87b7d344a3ec..37ae3afa7cef 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -429,6 +429,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() dragToDesktopTransitionHandler, mMockDesktopImmersiveController, userRepositories, + repositoryInitializer, recentsTransitionHandler, multiInstanceHelper, shellExecutor, @@ -5051,6 +5052,55 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() ) } + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun activateDesk_hasNonRunningTask_startsTask() { + val deskId = 0 + val nonRunningTask = + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0, background = true) + + val transition = Binder() + val deskChange = mock(TransitionInfo.Change::class.java) + whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull())) + .thenReturn(transition) + whenever(desksOrganizer.isDeskActiveAtEnd(deskChange, deskId)).thenReturn(true) + // Make desk inactive by activating another desk. + taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 1) + taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 1) + + controller.activateDesk(deskId, RemoteTransition(TestRemoteTransition())) + + val wct = getLatestWct(TRANSIT_TO_FRONT, OneShotRemoteHandler::class.java) + assertNotNull(wct) + wct.assertLaunchTask(nonRunningTask.taskId, WINDOWING_MODE_FREEFORM) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun activateDesk_hasRunningTask_reordersTask() { + val deskId = 0 + val runningTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + + val transition = Binder() + val deskChange = mock(TransitionInfo.Change::class.java) + whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull())) + .thenReturn(transition) + whenever(desksOrganizer.isDeskActiveAtEnd(deskChange, deskId)).thenReturn(true) + // Make desk inactive by activating another desk. + taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 1) + taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 1) + + controller.activateDesk(deskId, RemoteTransition(TestRemoteTransition())) + + verify(desksOrganizer).reorderTaskToFront(any(), eq(deskId), eq(runningTask)) + } + @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt index 6b2f90fc0a59..9af504797182 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt @@ -15,6 +15,7 @@ */ package com.android.wm.shell.desktopmode.multidesks +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.testing.AndroidTestingRunner import android.view.Display @@ -29,6 +30,7 @@ import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_R import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.ShellTaskOrganizer.TaskListener import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.LaunchAdjacentController @@ -39,14 +41,17 @@ import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellInit import com.google.common.truth.Truth.assertThat import kotlin.test.assertNotNull +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.kotlin.argThat import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever /** * Tests for [RootTaskDesksOrganizer]. @@ -76,48 +81,17 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { ) } - @Test - fun testCreateDesk_callsBack() { - val callback = FakeOnCreateCallback() - organizer.createDesk(Display.DEFAULT_DISPLAY, callback) - - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - - assertThat(callback.created).isTrue() - assertEquals(freeformRoot.taskId, callback.deskId) - } - - @Test - fun testCreateDesk_createsMinimizationRoot() { - val callback = FakeOnCreateCallback() - organizer.createDesk(Display.DEFAULT_DISPLAY, callback) - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - - val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(minimizationRootTask, SurfaceControl()) - - val minimizationRoot = organizer.deskMinimizationRootsByDeskId[freeformRoot.taskId] - assertNotNull(minimizationRoot) - assertThat(minimizationRoot.deskId).isEqualTo(freeformRoot.taskId) - assertThat(minimizationRoot.rootId).isEqualTo(minimizationRootTask.taskId) - } + @Test fun testCreateDesk_createsDeskAndMinimizationRoots() = runTest { createDesk() } @Test - fun testCreateMinimizationRoot_marksHidden() { - organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - - val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(minimizationRootTask, SurfaceControl()) + fun testCreateMinimizationRoot_marksHidden() = runTest { + val desk = createDesk() verify(mockShellTaskOrganizer) .applyTransaction( argThat { wct -> wct.changes.any { change -> - change.key == minimizationRootTask.token.asBinder() && + change.key == desk.minimizationRoot.token.asBinder() && (change.value.changeMask and Change.CHANGE_HIDDEN != 0) && change.value.hidden } @@ -126,7 +100,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testOnTaskAppeared_withoutRequest_throws() { + fun testOnTaskAppeared_withoutRequest_throws() = runTest { val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } assertThrows(Exception::class.java) { @@ -135,41 +109,25 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testOnTaskAppeared_withRequestOnlyInAnotherDisplay_throws() { - organizer.createDesk(displayId = 2, FakeOnCreateCallback()) - val freeformRoot = createFreeformTask(Display.DEFAULT_DISPLAY).apply { parentTaskId = -1 } - - assertThrows(Exception::class.java) { - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - } - } - - @Test - fun testOnTaskAppeared_duplicateRoot_throws() { - organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + fun testOnTaskAppeared_duplicateRoot_throws() = runTest { + val desk = createDesk() assertThrows(Exception::class.java) { - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + organizer.onTaskAppeared(desk.deskRoot.taskInfo, SurfaceControl()) } } @Test - fun testOnTaskAppeared_duplicateMinimizedRoot_throws() { - organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - organizer.onTaskAppeared(minimizationRootTask, SurfaceControl()) + fun testOnTaskAppeared_duplicateMinimizedRoot_throws() = runTest { + val desk = createDesk() assertThrows(Exception::class.java) { - organizer.onTaskAppeared(minimizationRootTask, SurfaceControl()) + organizer.onTaskAppeared(desk.minimizationRoot.taskInfo, SurfaceControl()) } } @Test - fun testOnTaskVanished_removesRoot() { + fun testOnTaskVanished_removesRoot() = runTest { val desk = createDesk() organizer.onTaskVanished(desk.deskRoot.taskInfo) @@ -178,7 +136,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testOnTaskVanished_removesMinimizedRoot() { + fun testOnTaskVanished_removesMinimizedRoot() = runTest { val desk = createDesk() organizer.onTaskVanished(desk.deskRoot.taskInfo) @@ -188,7 +146,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testDesktopWindowAppearsInDesk() { + fun testDesktopWindowAppearsInDesk() = runTest { val desk = createDesk() val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } @@ -198,7 +156,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testDesktopWindowAppearsInDeskMinimizationRoot() { + fun testDesktopWindowAppearsInDeskMinimizationRoot() = runTest { val desk = createDesk() val child = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId } @@ -208,7 +166,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testDesktopWindowMovesToMinimizationRoot() { + fun testDesktopWindowMovesToMinimizationRoot() = runTest { val desk = createDesk() val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } organizer.onTaskAppeared(child, SurfaceControl()) @@ -221,7 +179,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testDesktopWindowDisappearsFromDesk() { + fun testDesktopWindowDisappearsFromDesk() = runTest { val desk = createDesk() val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } @@ -232,7 +190,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testDesktopWindowDisappearsFromDeskMinimizationRoot() { + fun testDesktopWindowDisappearsFromDeskMinimizationRoot() = runTest { val desk = createDesk() val child = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId } @@ -243,7 +201,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testRemoveDesk_removesDeskRoot() { + fun testRemoveDesk_removesDeskRoot() = runTest { val desk = createDesk() val wct = WindowContainerTransaction() @@ -259,7 +217,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testRemoveDesk_removesMinimizationRoot() { + fun testRemoveDesk_removesMinimizationRoot() = runTest { val desk = createDesk() val wct = WindowContainerTransaction() @@ -275,7 +233,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testActivateDesk() { + fun testActivateDesk() = runTest { val desk = createDesk() val wct = WindowContainerTransaction() @@ -299,7 +257,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testActivateDesk_didNotExist_throws() { + fun testActivateDesk_didNotExist_throws() = runTest { val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } val wct = WindowContainerTransaction() @@ -307,7 +265,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testMoveTaskToDesk() { + fun testMoveTaskToDesk() = runTest { val desk = createDesk() val desktopTask = createFreeformTask().apply { parentTaskId = -1 } @@ -333,7 +291,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testMoveTaskToDesk_didNotExist_throws() { + fun testMoveTaskToDesk_didNotExist_throws() = runTest { val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } val desktopTask = createFreeformTask().apply { parentTaskId = -1 } @@ -344,7 +302,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testGetDeskAtEnd() { + fun testGetDeskAtEnd() = runTest { val desk = createDesk() val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } @@ -357,7 +315,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testGetDeskAtEnd_inMinimizationRoot() { + fun testGetDeskAtEnd_inMinimizationRoot() = runTest { val desk = createDesk() val task = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId } @@ -370,27 +328,24 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testIsDeskActiveAtEnd() { - organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - freeformRoot.isVisibleRequested = true - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + fun testIsDeskActiveAtEnd() = runTest { + val desk = createDesk() val isActive = organizer.isDeskActiveAtEnd( change = - TransitionInfo.Change(freeformRoot.token, SurfaceControl()).apply { - taskInfo = freeformRoot + TransitionInfo.Change(desk.deskRoot.token, SurfaceControl()).apply { + taskInfo = desk.deskRoot.taskInfo mode = TRANSIT_TO_FRONT }, - deskId = freeformRoot.taskId, + deskId = desk.deskRoot.deskId, ) assertThat(isActive).isTrue() } @Test - fun deactivateDesk_clearsLaunchRoot() { + fun deactivateDesk_clearsLaunchRoot() = runTest { val wct = WindowContainerTransaction() val desk = createDesk() organizer.activateDesk(wct, desk.deskRoot.deskId) @@ -409,7 +364,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun isDeskChange_forDeskId() { + fun isDeskChange_forDeskId() = runTest { val desk = createDesk() assertThat( @@ -424,7 +379,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun isDeskChange_forDeskId_inMinimizationRoot() { + fun isDeskChange_forDeskId_inMinimizationRoot() = runTest { val desk = createDesk() assertThat( @@ -442,7 +397,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun isDeskChange_anyDesk() { + fun isDeskChange_anyDesk() = runTest { val desk = createDesk() assertThat( @@ -456,7 +411,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun isDeskChange_anyDesk_inMinimizationRoot() { + fun isDeskChange_anyDesk_inMinimizationRoot() = runTest { val desk = createDesk() assertThat( @@ -473,7 +428,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun minimizeTask() { + fun minimizeTask() = runTest { val desk = createDesk() val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } val wct = WindowContainerTransaction() @@ -486,7 +441,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun minimizeTask_alreadyMinimized_noOp() { + fun minimizeTask_alreadyMinimized_noOp() = runTest { val desk = createDesk() val task = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId } val wct = WindowContainerTransaction() @@ -498,7 +453,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun minimizeTask_inDifferentDesk_noOp() { + fun minimizeTask_inDifferentDesk_noOp() = runTest { val desk = createDesk() val otherDesk = createDesk() val task = createFreeformTask().apply { parentTaskId = otherDesk.deskRoot.deskId } @@ -511,7 +466,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun unminimizeTask() { + fun unminimizeTask() = runTest { val desk = createDesk() val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } val wct = WindowContainerTransaction() @@ -528,7 +483,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun unminimizeTask_alreadyUnminimized_noOp() { + fun unminimizeTask_alreadyUnminimized_noOp() = runTest { val desk = createDesk() val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } val wct = WindowContainerTransaction() @@ -542,7 +497,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun unminimizeTask_notInDesk_noOp() { + fun unminimizeTask_notInDesk_noOp() = runTest { val desk = createDesk() val task = createFreeformTask() val wct = WindowContainerTransaction() @@ -553,7 +508,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun reorderTaskToFront() { + fun reorderTaskToFront() = runTest { val desk = createDesk() val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } val wct = WindowContainerTransaction() @@ -573,7 +528,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun reorderTaskToFront_notInDesk_noOp() { + fun reorderTaskToFront_notInDesk_noOp() = runTest { val desk = createDesk() val task = createFreeformTask() val wct = WindowContainerTransaction() @@ -592,7 +547,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun reorderTaskToFront_minimized_unminimizesAndReorders() { + fun reorderTaskToFront_minimized_unminimizesAndReorders() = runTest { val desk = createDesk() val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } val wct = WindowContainerTransaction() @@ -615,7 +570,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun onTaskAppeared_visibleDesk_onlyDesk_disablesLaunchAdjacent() { + fun onTaskAppeared_visibleDesk_onlyDesk_disablesLaunchAdjacent() = runTest { launchAdjacentController.launchAdjacentEnabled = true createDesk(visible = true) @@ -624,7 +579,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun onTaskAppeared_invisibleDesk_onlyDesk_enablesLaunchAdjacent() { + fun onTaskAppeared_invisibleDesk_onlyDesk_enablesLaunchAdjacent() = runTest { launchAdjacentController.launchAdjacentEnabled = false createDesk(visible = false) @@ -633,7 +588,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun onTaskAppeared_invisibleDesk_otherVisibleDesk_disablesLaunchAdjacent() { + fun onTaskAppeared_invisibleDesk_otherVisibleDesk_disablesLaunchAdjacent() = runTest { launchAdjacentController.launchAdjacentEnabled = true createDesk(visible = true) @@ -643,7 +598,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun onTaskInfoChanged_deskBecomesVisible_onlyDesk_disablesLaunchAdjacent() { + fun onTaskInfoChanged_deskBecomesVisible_onlyDesk_disablesLaunchAdjacent() = runTest { launchAdjacentController.launchAdjacentEnabled = true val desk = createDesk(visible = false) @@ -654,7 +609,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun onTaskInfoChanged_deskBecomesInvisible_onlyDesk_enablesLaunchAdjacent() { + fun onTaskInfoChanged_deskBecomesInvisible_onlyDesk_enablesLaunchAdjacent() = runTest { launchAdjacentController.launchAdjacentEnabled = false val desk = createDesk(visible = true) @@ -665,7 +620,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun onTaskInfoChanged_deskBecomesInvisible_otherVisibleDesk_disablesLaunchAdjacent() { + fun onTaskInfoChanged_deskBecomesInvisible_otherVisibleDesk_disablesLaunchAdjacent() = runTest { launchAdjacentController.launchAdjacentEnabled = true createDesk(visible = true) @@ -677,7 +632,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun onTaskVanished_visibleDeskDisappears_onlyDesk_enablesLaunchAdjacent() { + fun onTaskVanished_visibleDeskDisappears_onlyDesk_enablesLaunchAdjacent() = runTest { launchAdjacentController.launchAdjacentEnabled = false val desk = createDesk(visible = true) @@ -687,7 +642,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun onTaskVanished_visibleDeskDisappears_otherDeskVisible_disablesLaunchAdjacent() { + fun onTaskVanished_visibleDeskDisappears_otherDeskVisible_disablesLaunchAdjacent() = runTest { launchAdjacentController.launchAdjacentEnabled = true createDesk(visible = true) @@ -702,20 +657,39 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { val minimizationRoot: DeskMinimizationRoot, ) - private fun createDesk(visible: Boolean = true): DeskRoots { - organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) - val freeformRoot = + private suspend fun createDesk(visible: Boolean = true): DeskRoots { + val freeformRootTask = createFreeformTask().apply { parentTaskId = -1 isVisible = visible + isVisibleRequested = visible } - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - val minimizationRoot = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(minimizationRoot, SurfaceControl()) - return DeskRoots( - organizer.deskRootsByDeskId[freeformRoot.taskId], - checkNotNull(organizer.deskMinimizationRootsByDeskId[freeformRoot.taskId]), - ) + val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 } + Mockito.reset(mockShellTaskOrganizer) + whenever( + mockShellTaskOrganizer.createRootTask( + Display.DEFAULT_DISPLAY, + WINDOWING_MODE_FREEFORM, + organizer, + true, + ) + ) + .thenAnswer { invocation -> + val listener = (invocation.arguments[2] as TaskListener) + listener.onTaskAppeared(freeformRootTask, SurfaceControl()) + } + .thenAnswer { invocation -> + val listener = (invocation.arguments[2] as TaskListener) + listener.onTaskAppeared(minimizationRootTask, SurfaceControl()) + } + val deskId = organizer.createDesk(Display.DEFAULT_DISPLAY) + assertEquals(freeformRootTask.taskId, deskId) + val deskRoot = assertNotNull(organizer.deskRootsByDeskId.get(freeformRootTask.taskId)) + val minimizationRoot = + assertNotNull(organizer.deskMinimizationRootsByDeskId[freeformRootTask.taskId]) + assertThat(minimizationRoot.deskId).isEqualTo(freeformRootTask.taskId) + assertThat(minimizationRoot.rootId).isEqualTo(minimizationRootTask.taskId) + return DeskRoots(deskRoot, minimizationRoot) } private fun WindowContainerTransaction.hasMinimizationHops( @@ -738,14 +712,4 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { hop.newParent == desk.deskRoot.token.asBinder() && hop.toTop } - - private class FakeOnCreateCallback : DesksOrganizer.OnCreateCallback { - var deskId: Int? = null - val created: Boolean - get() = deskId != null - - override fun onCreated(deskId: Int) { - this.deskId = deskId - } - } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt index dd9e6ca0deae..4440d4e801fe 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.desktopmode.persistence import android.os.UserManager -import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY @@ -82,10 +81,27 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { } @Test - @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM) - /** TODO: b/362720497 - add multi-desk version when implemented. */ - @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) - fun initWithPersistence_multipleUsers_addedCorrectly_multiDesksDisabled() = + fun init_updatesFlow() = + runTest(StandardTestDispatcher()) { + whenever(persistentRepository.getUserDesktopRepositoryMap()) + .thenReturn(mapOf(USER_ID_1 to desktopRepositoryState1)) + whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1)) + .thenReturn(desktopRepositoryState1) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)).thenReturn(desktop1) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)).thenReturn(desktop2) + + repositoryInitializer.initialize(desktopUserRepositories) + + assertThat(repositoryInitializer.isInitialized.value).isTrue() + } + + @Test + @EnableFlags( + FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, + FLAG_ENABLE_DESKTOP_WINDOWING_HSUM, + FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun initWithPersistence_multipleUsers_addedCorrectly() = runTest(StandardTestDispatcher()) { whenever(persistentRepository.getUserDesktopRepositoryMap()) .thenReturn( @@ -104,50 +120,74 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { repositoryInitializer.initialize(desktopUserRepositories) - // Desktop Repository currently returns all tasks across desktops for a specific user - // since the repository currently doesn't handle desktops. This test logic should be - // updated - // once the repository handles multiple desktops. assertThat( - desktopUserRepositories.getProfile(USER_ID_1).getActiveTasks(DEFAULT_DISPLAY) + desktopUserRepositories + .getProfile(USER_ID_1) + .getActiveTaskIdsInDesk(DESKTOP_ID_1) ) - .containsExactly(1, 3, 4, 5) + .containsExactly(1, 3) .inOrder() assertThat( desktopUserRepositories .getProfile(USER_ID_1) - .getExpandedTasksOrdered(DEFAULT_DISPLAY) + .getActiveTaskIdsInDesk(DESKTOP_ID_2) ) - .containsExactly(5, 1) + .containsExactly(4, 5) .inOrder() assertThat( - desktopUserRepositories.getProfile(USER_ID_1).getMinimizedTasks(DEFAULT_DISPLAY) + desktopUserRepositories + .getProfile(USER_ID_2) + .getActiveTaskIdsInDesk(DESKTOP_ID_3) ) - .containsExactly(3, 4) + .containsExactly(7, 8) + .inOrder() + assertThat( + desktopUserRepositories + .getProfile(USER_ID_1) + .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_1) + ) + .containsExactly(1) .inOrder() - assertThat( - desktopUserRepositories.getProfile(USER_ID_2).getActiveTasks(DEFAULT_DISPLAY) + desktopUserRepositories + .getProfile(USER_ID_1) + .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_2) ) - .containsExactly(7, 8) + .containsExactly(5) .inOrder() assertThat( desktopUserRepositories .getProfile(USER_ID_2) - .getExpandedTasksOrdered(DEFAULT_DISPLAY) + .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_3) + ) + .containsExactly(7) + .inOrder() + assertThat( + desktopUserRepositories + .getProfile(USER_ID_1) + .getMinimizedTaskIdsInDesk(DESKTOP_ID_1) + ) + .containsExactly(3) + .inOrder() + assertThat( + desktopUserRepositories + .getProfile(USER_ID_1) + .getMinimizedTaskIdsInDesk(DESKTOP_ID_2) ) - .contains(7) + .containsExactly(4) + .inOrder() assertThat( - desktopUserRepositories.getProfile(USER_ID_2).getMinimizedTasks(DEFAULT_DISPLAY) + desktopUserRepositories + .getProfile(USER_ID_2) + .getMinimizedTaskIdsInDesk(DESKTOP_ID_3) ) .containsExactly(8) + .inOrder() } @Test - @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) - /** TODO: b/362720497 - add multi-desk version when implemented. */ - @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) - fun initWithPersistence_singleUser_addedCorrectly_multiDesksDisabled() = + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun initWithPersistence_singleUser_addedCorrectly() = runTest(StandardTestDispatcher()) { whenever(persistentRepository.getUserDesktopRepositoryMap()) .thenReturn(mapOf(USER_ID_1 to desktopRepositoryState1)) @@ -161,23 +201,44 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { assertThat( desktopUserRepositories .getProfile(USER_ID_1) - .getActiveTaskIdsInDesk(deskId = DEFAULT_DISPLAY) + .getActiveTaskIdsInDesk(DESKTOP_ID_1) + ) + .containsExactly(1, 3) + .inOrder() + assertThat( + desktopUserRepositories + .getProfile(USER_ID_1) + .getActiveTaskIdsInDesk(DESKTOP_ID_2) + ) + .containsExactly(4, 5) + .inOrder() + assertThat( + desktopUserRepositories + .getProfile(USER_ID_1) + .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_1) + ) + .containsExactly(1) + .inOrder() + assertThat( + desktopUserRepositories + .getProfile(USER_ID_1) + .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_2) ) - .containsExactly(1, 3, 4, 5) + .containsExactly(5) .inOrder() assertThat( desktopUserRepositories .getProfile(USER_ID_1) - .getExpandedTasksIdsInDeskOrdered(deskId = DEFAULT_DISPLAY) + .getMinimizedTaskIdsInDesk(DESKTOP_ID_1) ) - .containsExactly(5, 1) + .containsExactly(3) .inOrder() assertThat( desktopUserRepositories .getProfile(USER_ID_1) - .getMinimizedTaskIdsInDesk(deskId = DEFAULT_DISPLAY) + .getMinimizedTaskIdsInDesk(DESKTOP_ID_2) ) - .containsExactly(3, 4) + .containsExactly(4) .inOrder() } -- cgit v1.2.3-59-g8ed1b From a87924539a642e40ad6915bd3c6b69b2e377395a Mon Sep 17 00:00:00 2001 From: Jorge Gil Date: Thu, 6 Mar 2025 03:11:19 +0000 Subject: [40/N] Desks: Fix failing WMShellUnitTests Also adds the missing setFlagParameterization call in DesktopTasksController needed for the tests to actually run with the flag enabled. Flag: com.android.window.flags.enable_multiple_desktops_backend Bug: 362720497 Test: atest WMShellUnitTests Change-Id: I05ef9baff29d72b792943f0d850e9e079ed2c645 --- .../wm/shell/desktopmode/DesktopRepository.kt | 7 +- .../desktopmode/DesktopImmersiveControllerTest.kt | 3 + .../desktopmode/DesktopTasksControllerTest.kt | 386 +++++++++++++++++++-- 3 files changed, 356 insertions(+), 40 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index 4e20c292445d..8636bc1f56c2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -716,12 +716,15 @@ class DesktopRepository( } /** - * Returns the top transparent fullscreen task id for a given display's active desk, or null. + * Returns the top transparent fullscreen task id for a given display, or null. * * TODO: b/389960283 - add explicit [deskId] argument. */ fun getTopTransparentFullscreenTaskId(displayId: Int): Int? = - desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId + desktopData + .desksSequence(displayId) + .mapNotNull { it.topTransparentFullscreenTaskId } + .firstOrNull() /** * Clears the top transparent fullscreen task id info for a given display's active desk. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt index 006c3cae121c..4c18ee1500b7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt @@ -114,6 +114,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() { transactionSupplier = transactionSupplier, ) desktopRepository = userRepositories.current + desktopRepository.addDesk(DEFAULT_DISPLAY, DEFAULT_DESK_ID) + desktopRepository.setActiveDesk(DEFAULT_DISPLAY, DEFAULT_DESK_ID) } @Test @@ -835,5 +837,6 @@ class DesktopImmersiveControllerTest : ShellTestCase() { companion object { private val STABLE_BOUNDS = Rect(0, 100, 2000, 1900) private val DISPLAY_BOUNDS = Rect(0, 0, 2000, 2000) + private const val DEFAULT_DESK_ID = 0 } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 37ae3afa7cef..a66b77747eee 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -22,6 +22,7 @@ import android.app.ActivityOptions import android.app.KeyguardManager import android.app.PendingIntent import android.app.PictureInPictureParams +import android.app.WindowConfiguration import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM @@ -294,6 +295,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() private val wallpaperToken = MockToken().token() private val homeComponentName = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "") + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + @Before fun setUp() { Dispatchers.setMain(StandardTestDispatcher()) @@ -623,11 +628,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isDesktopModeShowing_noTasks_returnsFalse() { assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isDesktopModeShowing_noTasksVisible_returnsFalse() { val task1 = setUpFreeformTask() val task2 = setUpFreeformTask() @@ -638,6 +645,15 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun isDesktopModeShowing_noActiveDesk_returnsFalse() { + taskRepository.setDeskInactive(deskId = 0) + + assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse() + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() { val task1 = setUpFreeformTask() val task2 = setUpFreeformTask() @@ -652,6 +668,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, ) + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isDesktopModeShowing_topTransparentFullscreenTask_returnsTrue() { val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId) @@ -659,6 +676,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isTrue() } + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun isDesktopModeShowing_deskInactive_topTransparentFullscreenTask_returnsTrue() { + val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId) + taskRepository.setDeskInactive(deskId = 0) + + assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isTrue() + } + @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, @@ -1049,11 +1080,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isAnyDeskActive_noTasks_returnsFalse() { assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isFalse() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun isAnyDeskActive_noActiveDesk_returnsFalse() { + taskRepository.setDeskInactive(deskId = 0) + + assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun isAnyDeskActive_withActiveDesk_returnsTrue() { + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + + assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isTrue() + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isAnyDeskActive_twoTasks_bothVisible_returnsTrue() { setUpHomeTask() @@ -1064,6 +1113,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isInDesktop_twoTasks_oneVisible_returnsTrue() { setUpHomeTask() @@ -1074,6 +1124,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isAnyDeskActive_twoTasksVisibleOnDifferentDisplays_returnsTrue() { taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) @@ -1093,7 +1144,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) - assertThat(finalBounds).isEqualTo(Rect()) + assertThat(finalBounds).isNull() } @Test @@ -1104,7 +1155,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) - assertThat(finalBounds).isEqualTo(Rect()) + assertThat(finalBounds).isNull() } @Test @@ -1115,7 +1166,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) - assertThat(finalBounds).isEqualTo(Rect()) + assertThat(finalBounds).isNull() } @Test @@ -1126,7 +1177,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) - assertThat(finalBounds).isEqualTo(Rect()) + assertThat(finalBounds).isNull() } @Test @@ -1810,6 +1861,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val wallpaperToken = MockToken().token() whenever(desktopWallpaperActivityTokenProvider.getToken(SECOND_DISPLAY)) .thenReturn(wallpaperToken) + taskRepository.addDesk(SECOND_DISPLAY, deskId = 2) val task = setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = 2, background = true) controller.moveTaskToDefaultDeskAndActivate( @@ -2614,7 +2666,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun moveToNextDisplay_moveFromFirstToSecondDisplay() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_moveFromFirstToSecondDisplay_multiDesksDisabled() { // Set up two display ids taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) whenever(rootTaskDisplayAreaOrganizer.displayIds) @@ -2640,7 +2693,27 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun moveToNextDisplay_moveFromSecondToFirstDisplay() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_moveFromFirstToSecondDisplay_multiDesksEnabled() { + // Set up two display ids + val targetDeskId = 2 + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId) + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: second display + val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) + .thenReturn(secondDisplayArea) + + val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + controller.moveToNextDisplay(task.taskId) + + verify(desksOrganizer).moveTaskToDesk(any(), eq(targetDeskId), eq(task)) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_moveFromSecondToFirstDisplay_multiDesksDisabled() { // Set up two display ids whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) @@ -2665,6 +2738,25 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() assertThat(taskChange.toTop).isTrue() } + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_moveFromSecondToFirstDisplay_multiDesksEnabled() { + // Set up two display ids + val targetDeskId = 0 + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: default display + val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) + .thenReturn(defaultDisplayArea) + + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) + val task = setUpFreeformTask(displayId = SECOND_DISPLAY) + controller.moveToNextDisplay(task.taskId) + + verify(desksOrganizer).moveTaskToDesk(any(), eq(targetDeskId), eq(task)) + } + @Test @EnableFlags( FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, @@ -2729,6 +2821,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT) fun moveToNextDisplay_sizeInDpPreserved() { // Set up two display ids + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2) whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) // Create a mock for the target display area: second display @@ -2770,6 +2863,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT) fun moveToNextDisplay_shiftWithinDestinationDisplayBounds() { // Set up two display ids + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2) whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) // Create a mock for the target display area: second display @@ -2811,6 +2905,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() // Set up two display ids whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2) // Create a mock for the target display area: second display val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) @@ -2919,7 +3014,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun moveToNextDisplay_toDesktopInOtherDisplay_bringsExistingTasksToFront() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_toDesktopInOtherDisplay_multiDesksDisabled_bringsExistingTasksToFront() { val transition = Binder() val sourceDeskId = 0 val targetDeskId = 2 @@ -2944,6 +3040,34 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() wct.assertReorder(task2.token, /* toTop= */ true) } + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_toDesktopInOtherDisplay_multiDesksEnabled_bringsExistingTasksToFront() { + val transition = Binder() + val sourceDeskId = 0 + val targetDeskId = 2 + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId) + taskRepository.setDeskInactive(deskId = targetDeskId) + // Set up two display ids + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: second display + val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) + .thenReturn(secondDisplayArea) + whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull())) + .thenReturn(transition) + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId) + val task2 = setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = targetDeskId) + + controller.moveToNextDisplay(task1.taskId) + + // Existing desktop task in the target display is moved to front. + val wct = getLatestTransition() + assertNotNull(wct) + verify(desksOrganizer).reorderTaskToFront(wct, targetDeskId, task2) + } + @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, @@ -3058,7 +3182,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test fun moveToNextDisplay_movingToDesktop_sendsTaskbarRoundingUpdate() { val transition = Binder() - val sourceDeskId = 1 + val sourceDeskId = 0 val targetDeskId = 2 taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId) taskRepository.setDeskInactive(deskId = targetDeskId) @@ -3575,7 +3699,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTask_freeformVisible_multiDesksDisabled_returnSwitchToFreeformWCT() { val homeTask = setUpHomeTask() val freeformTask = setUpFreeformTask() markTaskVisible(freeformTask) @@ -3591,7 +3716,22 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_returnSwitchToFreeformWCT() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTask_deskActive_multiDesksEnabled_movesToDesk() { + val deskId = 0 + taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = deskId) + setUpHomeTask() + val fullscreenTask = createFullscreenTask() + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct, "should handle request") + verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_multiDesksDisabled_returnSwitchToFreeformWCT() { val homeTask = setUpHomeTask() val freeformTask = setUpFreeformTask() markTaskVisible(freeformTask) @@ -3615,6 +3755,21 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() wct.assertReorderAt(4, fullscreenTask, toTop = true) } + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_activeDesk_multiDesksEnabled_movesToDesk() { + val deskId = 0 + setUpHomeTask() + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct, "should handle request") + verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask) + } + @Test @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_fullscreenTaskToDesk_underTaskLimit_multiDesksDisabled_dontMinimize() { @@ -3847,6 +4002,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() { whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) @@ -3869,7 +4025,28 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTask_noTasks_enforceDesktop_fullscreenDisplay_returnNull() { + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_fullscreenTask_noInDesk_enforceDesktop_freeformDisplay_movesToDesk() { + val deskId = 0 + taskRepository.setDeskInactive(deskId) + whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) + whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + + val fullscreenTask = createFullscreenTask() + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct, "should handle request") + verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask) + } + + @Test + fun handleRequest_fullscreenTask_notInDesk_enforceDesktop_fullscreenDisplay_returnNull() { + taskRepository.setDeskInactive(deskId = 0) whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN @@ -3881,6 +4058,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() { val freeformTask = setUpFreeformTask() markTaskHidden(freeformTask) @@ -3889,12 +4067,23 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_fullscreenTask_noOtherTasks_returnNull() { val fullscreenTask = createFullscreenTask() assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTask_notInDesk_returnNull() { + taskRepository.setDeskInactive(deskId = 0) + val fullscreenTask = createFullscreenTask() + + assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() { val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY) createFreeformTask(displayId = SECOND_DISPLAY) @@ -3904,6 +4093,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() assertThat(result).isNull() } + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTask_deskInOtherDisplayActive_returnNull() { + taskRepository.setDeskInactive(deskId = 0) + val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY) + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2) + taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = 2) + + val result = + controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay)) + + assertThat(result).isNull() + } + @Test @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_multiDesksDisabled_minimize() { @@ -3956,7 +4159,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() { - val freeformTask = setUpFreeformTask() + taskRepository.setDeskInactive(deskId = 0) + val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) markTaskHidden(freeformTask) val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) @@ -3986,9 +4190,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN - - val freeformTask = setUpFreeformTask() + taskRepository.setDeskInactive(deskId = 0) + val freeformTask = setUpFreeformTask(DEFAULT_DISPLAY, deskId = 0) markTaskHidden(freeformTask) + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) assertNotNull(wct, "should handle request") @@ -3997,7 +4202,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + @DisableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() { val freeformTask1 = setUpFreeformTask() val freeformTask2 = createFreeformTask() @@ -4016,7 +4224,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_multiDesksDisabled_reorderedToTop() { whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) val freeformTask1 = setUpFreeformTask() val freeformTask2 = createFreeformTask() @@ -4039,34 +4248,47 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() { - val task = createFreeformTask() - val result = controller.handleRequest(Binder(), createTransition(task)) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_freeformTask_desktopWallpaperEnabled_notInDesk_reorderedToTop() { + whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) + val deskId = 0 + taskRepository.setDeskInactive(deskId) + val freeformTask1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + val freeformTask2 = createFreeformTask() - assertNotNull(result, "Should handle request") - assertThat(result.hierarchyOps?.size).isEqualTo(1) - result.assertReorderAt(0, task, toTop = true) + val wct = + controller.handleRequest( + Binder(), + createTransition(freeformTask2, type = TRANSIT_TO_FRONT), + ) + + assertNotNull(wct, "Should handle request") + verify(desksOrganizer).reorderTaskToFront(wct, deskId, freeformTask1) + wct.assertReorder(freeformTask2, toTop = true) } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() { - whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) + @DisableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() { val task = createFreeformTask() - val result = controller.handleRequest(Binder(), createTransition(task)) assertNotNull(result, "Should handle request") - assertThat(result.hierarchyOps?.size).isEqualTo(2) - // Add desktop wallpaper activity - result.assertPendingIntentAt(0, desktopWallpaperIntent) - // Bring new task to front - result.assertReorderAt(1, task, toTop = true) + assertThat(result.hierarchyOps?.size).isEqualTo(1) + result.assertReorderAt(0, task, toTop = true) } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + @DisableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() { val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) // Second display task @@ -4082,10 +4304,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() { + taskRepository.setDeskInactive(deskId = 0) whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) // Second display task - createFreeformTask(displayId = SECOND_DISPLAY) + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2) + taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = 2) + setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = 2) val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay)) @@ -4221,7 +4446,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_topActivityTransparentWithoutDisplay_multiDesksDisabled_returnSwitchToFreeformWCT() { val freeformTask = setUpFreeformTask() markTaskVisible(freeformTask) @@ -4237,6 +4463,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() .isEqualTo(WINDOWING_MODE_FREEFORM) } + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_topActivityTransparentWithoutDisplay_multiDesksEnabled_returnSwitchToFreeformWCT() { + val deskId = 0 + val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + markTaskVisible(freeformTask) + + val task = + setUpFullscreenTask().apply { + isActivityStackTransparent = true + isTopActivityNoDisplay = true + numActivities = 1 + } + + val wct = controller.handleRequest(Binder(), createTransition(task)) + + assertNotNull(wct) + verify(desksOrganizer).moveTaskToDesk(wct, deskId, task) + } + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION) @@ -4296,7 +4545,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, ) - fun handleRequest_onlyTopTransparentFullscreenTask_returnSwitchToFreeformWCT() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_onlyTopTransparentFullscreenTask_multiDesksDisabled_returnSwitchToFreeformWCT() { val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId) @@ -4307,9 +4557,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() .isEqualTo(WINDOWING_MODE_FREEFORM) } + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_onlyTopTransparentFullscreenTask_multiDesksEnabled_movesToDesktop() { + val deskId = 0 + val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId) + taskRepository.setDeskInactive(deskId = deskId) + + val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + + val wct = controller.handleRequest(Binder(), createTransition(task)) + assertNotNull(wct) + verify(desksOrganizer).moveTaskToDesk(wct, deskId, task) + } + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun handleRequest_desktopNotShowing_topTransparentFullscreenTask_returnNull() { + taskRepository.setDeskInactive(deskId = 0) val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() @@ -4363,7 +4633,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_systemUIActivityWithoutDisplay_multiDesksDisabled_returnSwitchToFreeformWCT() { val freeformTask = setUpFreeformTask() markTaskVisible(freeformTask) @@ -4382,6 +4653,32 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() .isEqualTo(WINDOWING_MODE_FREEFORM) } + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_systemUIActivityWithoutDisplay_multiDesksEnabled_movesTaskToDesk() { + val deskId = 0 + val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + markTaskVisible(freeformTask) + + // Set task as systemUI package + val systemUIPackageName = + context.resources.getString(com.android.internal.R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "") + val task = + setUpFullscreenTask(displayId = DEFAULT_DISPLAY).apply { + baseActivity = baseComponent + isTopActivityNoDisplay = true + } + + val wct = controller.handleRequest(Binder(), createTransition(task)) + + assertNotNull(wct) + verify(desksOrganizer).moveTaskToDesk(wct, deskId, task) + } + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun handleRequest_defaultHomePackageWithDisplay_returnSwitchToFullscreenWCT() { val freeformTask = setUpFreeformTask() @@ -4424,6 +4721,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT_enforcedDesktop() { + taskRepository.setDeskInactive(deskId = 0) whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM @@ -6830,6 +7128,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_notShowingDesktop_doesNotPlay() { + taskRepository.setDeskInactive(deskId = 0) val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) taskRepository.setTaskInFullImmersiveState( displayId = triggerTask.displayId, @@ -6906,6 +7205,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_fullscreenStaysFullscreen_doesNotPlay() { val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + taskRepository.setDeskInactive(deskId = 0) assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse() assertThat( @@ -6941,6 +7241,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_freeformExitsDesktop_doesNotPlay() { val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, active = false) + taskRepository.setDeskInactive(deskId = 0) assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse() assertThat( @@ -6970,6 +7271,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, ) fun startLaunchTransition_desktopNotShowing_movesWallpaperToFront() { + taskRepository.setDeskInactive(deskId = 0) val launchingTask = createFreeformTask(displayId = DEFAULT_DISPLAY) val wct = WindowContainerTransaction() wct.reorder(launchingTask.token, /* onTop= */ true) @@ -7439,7 +7741,15 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? = - wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds + wct.changes.entries + .find { (token, change) -> + token == task.token.asBinder() && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 + } + ?.value + ?.configuration + ?.windowConfiguration + ?.bounds private fun verifyWCTNotExecuted() { verify(transitions, never()).startTransition(anyInt(), any(), isNull()) -- cgit v1.2.3-59-g8ed1b