summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt109
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/OnDeskRemovedListener.kt22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt182
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt78
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt121
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt84
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt54
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt256
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt27
17 files changed, 981 insertions, 134 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 fdfaa90ac8b9..c64c98750b0a 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
@@ -108,6 +108,8 @@ import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
import com.android.wm.shell.desktopmode.education.AppToWebEducationFilter;
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository;
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer;
+import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer;
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer;
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializerImpl;
@@ -703,6 +705,16 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static DesksOrganizer provideDesksOrganizer(
+ @NonNull ShellInit shellInit,
+ @NonNull ShellCommandHandler shellCommandHandler,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer
+ ) {
+ return new RootTaskDesksOrganizer(shellInit, shellCommandHandler, shellTaskOrganizer);
+ }
+
+ @WMSingleton
+ @Provides
@DynamicOverride
static DesktopTasksController provideDesktopTasksController(
Context context,
@@ -741,7 +753,8 @@ public abstract class WMShellModule {
DesktopTilingDecorViewModel desktopTilingDecorViewModel,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
Optional<BubbleController> bubbleController,
- OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver) {
+ OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
+ DesksOrganizer desksOrganizer) {
return new DesktopTasksController(
context,
shellInit,
@@ -775,7 +788,8 @@ public abstract class WMShellModule {
desktopTilingDecorViewModel,
desktopWallpaperActivityTokenProvider,
bubbleController,
- overviewToDesktopTransitionObserver);
+ overviewToDesktopTransitionObserver,
+ desksOrganizer);
}
@WMSingleton
@@ -1183,10 +1197,11 @@ public abstract class WMShellModule {
Transitions transitions,
DisplayController displayController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- IWindowManager windowManager
+ IWindowManager windowManager,
+ Optional<DesktopUserRepositories> desktopUserRepositories,
+ Optional<DesktopTasksController> desktopTasksController
) {
- if (!DesktopModeStatus.canEnterDesktopMode(context)
- || !Flags.enableDisplayWindowingModeSwitching()) {
+ if (!DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.empty();
}
return Optional.of(
@@ -1196,7 +1211,9 @@ public abstract class WMShellModule {
transitions,
displayController,
rootTaskDisplayAreaOrganizer,
- windowManager));
+ windowManager,
+ desktopUserRepositories.get(),
+ desktopTasksController.get()));
}
@WMSingleton
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 43e8d2a30930..760d2124b845 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
@@ -24,9 +24,14 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.WindowContainerTransaction
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
+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 com.android.wm.shell.transition.Transitions
@@ -38,7 +43,12 @@ class DesktopDisplayEventHandler(
private val displayController: DisplayController,
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
private val windowManager: IWindowManager,
-) : OnDisplaysChangedListener {
+ private val desktopUserRepositories: DesktopUserRepositories,
+ private val desktopTasksController: DesktopTasksController,
+) : OnDisplaysChangedListener, OnDeskRemovedListener {
+
+ private val desktopRepository: DesktopRepository
+ get() = desktopUserRepositories.current
init {
shellInit.addInitCallback({ onInit() }, this)
@@ -46,23 +56,43 @@ class DesktopDisplayEventHandler(
private fun onInit() {
displayController.addDisplayWindowListener(this)
+
+ if (Flags.enableMultipleDesktopsBackend()) {
+ desktopTasksController.onDeskRemovedListener = this
+ }
}
override fun onDisplayAdded(displayId: Int) {
- if (displayId == DEFAULT_DISPLAY) {
+ if (displayId != DEFAULT_DISPLAY) {
+ refreshDisplayWindowingMode()
+ }
+
+ if (!supportsDesks(displayId)) {
+ logV("Display #$displayId does not support desks")
return
}
- refreshDisplayWindowingMode()
+ logV("Creating new desk in new display#$displayId")
+ desktopTasksController.createDesk(displayId)
}
override fun onDisplayRemoved(displayId: Int) {
- if (displayId == DEFAULT_DISPLAY) {
- return
+ if (displayId != DEFAULT_DISPLAY) {
+ refreshDisplayWindowingMode()
+ }
+
+ // TODO: b/362720497 - move desks in closing display to the remaining desk.
+ }
+
+ override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
+ val remainingDesks = desktopRepository.getNumberOfDesks(lastDisplayId)
+ if (remainingDesks == 0) {
+ logV("All desks removed from display#$lastDisplayId, creating empty desk")
+ desktopTasksController.createDesk(lastDisplayId)
}
- refreshDisplayWindowingMode()
}
private fun refreshDisplayWindowingMode() {
+ if (!Flags.enableDisplayWindowingModeSwitching()) return
// TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
val isExtendedDisplayEnabled =
0 !=
@@ -98,4 +128,16 @@ class DesktopDisplayEventHandler(
wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
}
+
+ // TODO: b/362720497 - connected/projected display considerations.
+ private fun supportsDesks(displayId: Int): Boolean =
+ DesktopModeStatus.canEnterDesktopMode(context)
+
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ companion object {
+ private const val TAG = "DesktopDisplayEventHandler"
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index 9b9988457808..164d04bbde65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -110,8 +110,8 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
pw.println("Error: display id should be an integer")
return false
}
- pw.println("Not implemented.")
- return false
+ controller.createDesk(displayId)
+ return true
}
private fun runActivateDesk(args: Array<String>, pw: PrintWriter): Boolean {
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 fa696682de28..6636770895fa 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
@@ -171,6 +171,9 @@ class DesktopRepository(
/** Returns a list of all [Desk]s in the repository. */
private fun desksSequence(): Sequence<Desk> = desktopData.desksSequence()
+ /** Returns the number of desks in the given display. */
+ fun getNumberOfDesks(displayId: Int) = desktopData.getNumberOfDesks(displayId)
+
/** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */
fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
desktopGestureExclusionListener = regionListener
@@ -201,11 +204,11 @@ class DesktopRepository(
/** Adds the given desk under the given display. */
fun addDesk(displayId: Int, deskId: Int) {
- desktopData.getOrCreateDesk(displayId, deskId)
+ desktopData.createDesk(displayId, deskId)
}
/** Returns the default desk in the given display. */
- fun getDefaultDesk(displayId: Int): Int? = desktopData.getDefaultDesk(displayId)?.deskId
+ private fun getDefaultDesk(displayId: Int): Desk? = desktopData.getDefaultDesk(displayId)
/** Sets the given desk as the active one in the given display. */
fun setActiveDesk(displayId: Int, deskId: Int) {
@@ -229,15 +232,14 @@ class DesktopRepository(
* TODO: b/389960283 - add explicit [deskId] argument.
*/
private fun addActiveTask(displayId: Int, taskId: Int) {
- val activeDeskId =
- desktopData.getActiveDesk(displayId)?.deskId
- ?: error("Expected active desk in display: $displayId")
+ val activeDesk = desktopData.getDefaultDesk(displayId)
+ checkNotNull(activeDesk) { "Expected desk in display: $displayId" }
// Removes task if it is active on another desk excluding [activeDesk].
- removeActiveTask(taskId, excludedDeskId = activeDeskId)
+ removeActiveTask(taskId, excludedDeskId = activeDesk.deskId)
- if (desktopData.getOrCreateDesk(displayId, activeDeskId).activeTasks.add(taskId)) {
- logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId)
+ if (activeDesk.activeTasks.add(taskId)) {
+ logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, activeDesk.deskId)
updateActiveTasksListeners(displayId)
}
}
@@ -266,18 +268,23 @@ class DesktopRepository(
* TODO: b/389960283 - add explicit [deskId] argument.
*/
fun addClosingTask(displayId: Int, taskId: Int) {
- val activeDeskId =
- desktopData.getActiveDesk(displayId)?.deskId
+ val activeDesk =
+ desktopData.getActiveDesk(displayId)
?: error("Expected active desk in display: $displayId")
- if (desktopData.getOrCreateDesk(displayId, activeDeskId).closingTasks.add(taskId)) {
- logD("Added closing task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId)
+ if (activeDesk.closingTasks.add(taskId)) {
+ logD(
+ "Added closing task=%d displayId=%d deskId=%d",
+ taskId,
+ displayId,
+ activeDesk.deskId,
+ )
} else {
// If the task hasn't been removed from closing list after it disappeared.
logW(
"Task with taskId=%d displayId=%d deskId=%d is already closing",
taskId,
displayId,
- activeDeskId,
+ activeDesk.deskId,
)
}
}
@@ -323,7 +330,7 @@ class DesktopRepository(
/**
* Returns the active tasks in the given display's active desk.
*
- * TODO: b/389960283 - add explicit [deskId] argument.
+ * TODO: b/389960283 - migrate callers to [getActiveTaskIdsInDesk].
*/
@VisibleForTesting
fun getActiveTasks(displayId: Int): ArraySet<Int> =
@@ -332,19 +339,27 @@ class DesktopRepository(
/**
* Returns the minimized tasks in the given display's active desk.
*
- * TODO: b/389960283 - add explicit [deskId] argument.
+ * TODO: b/389960283 - migrate callers to [getMinimizedTaskIdsInDesk].
*/
fun getMinimizedTasks(displayId: Int): ArraySet<Int> =
ArraySet(desktopData.getActiveDesk(displayId)?.minimizedTasks)
+ @VisibleForTesting
+ fun getMinimizedTaskIdsInDesk(deskId: Int): ArraySet<Int> =
+ ArraySet(desktopData.getDesk(deskId)?.minimizedTasks)
+
/**
* Returns all active non-minimized tasks for [displayId] ordered from top to bottom.
*
- * TODO: b/389960283 - add explicit [deskId] argument.
+ * TODO: b/389960283 - migrate callers to [getExpandedTasksIdsInDeskOrdered].
*/
fun getExpandedTasksOrdered(displayId: Int): List<Int> =
getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) }
+ @VisibleForTesting
+ fun getExpandedTasksIdsInDeskOrdered(deskId: Int): List<Int> =
+ getFreeformTasksIdsInDeskInZOrder(deskId).filter { !isMinimizedTask(it) }
+
/**
* Returns the count of active non-minimized tasks for [displayId].
*
@@ -357,11 +372,15 @@ class DesktopRepository(
/**
* Returns a list of freeform tasks, ordered from top-bottom (top at index 0).
*
- * TODO: b/389960283 - add explicit [deskId] argument.
+ * TODO: b/389960283 - migrate callers to [getFreeformTasksIdsInDeskInZOrder].
*/
@VisibleForTesting
fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> =
- ArrayList(desktopData.getActiveDesk(displayId)?.freeformTasksInZOrder ?: emptyList())
+ ArrayList(desktopData.getDefaultDesk(displayId)?.freeformTasksInZOrder ?: emptyList())
+
+ @VisibleForTesting
+ fun getFreeformTasksIdsInDeskInZOrder(deskId: Int): ArrayList<Int> =
+ ArrayList(desktopData.getDesk(deskId)?.freeformTasksInZOrder ?: emptyList())
/** Returns the tasks inside the given desk. */
fun getActiveTaskIdsInDesk(deskId: Int): Set<Int> =
@@ -401,8 +420,8 @@ class DesktopRepository(
}
val prevCount = getVisibleTaskCount(displayId)
if (isVisible) {
- desktopData.getActiveDesk(displayId)?.visibleTasks?.add(taskId)
- ?: error("Expected non-null active desk in display $displayId")
+ desktopData.getDefaultDesk(displayId)?.visibleTasks?.add(taskId)
+ ?: error("Expected non-null desk in display $displayId")
unminimizeTask(displayId, taskId)
} else {
desktopData.getActiveDesk(displayId)?.visibleTasks?.remove(taskId)
@@ -587,17 +606,15 @@ class DesktopRepository(
* TODO: b/389960283 - add explicit [deskId] argument.
*/
private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
- val activeDesk =
- desktopData.getActiveDesk(displayId)
- ?: error("Expected a desk to be active in display: $displayId")
+ val desk = getDefaultDesk(displayId) ?: error("Expected a desk in display: $displayId")
logD(
"Add or move task to top: display=%d taskId=%d deskId=%d",
taskId,
displayId,
- activeDesk.deskId,
+ desk.deskId,
)
- desktopData.forAllDesks { _, desk -> desk.freeformTasksInZOrder.remove(taskId) }
- activeDesk.freeformTasksInZOrder.add(0, taskId)
+ desktopData.forAllDesks { _, desk1 -> desk1.freeformTasksInZOrder.remove(taskId) }
+ desk.freeformTasksInZOrder.add(0, taskId)
// Unminimize the task if it is minimized.
unminimizeTask(displayId, taskId)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -835,13 +852,8 @@ class DesktopRepository(
/** An interface for the desktop hierarchy's data managed by this repository. */
private interface DesktopData {
- /**
- * Returns the existing desk or creates a new entry if needed.
- *
- * TODO: 389787966 - consider removing this as it cannot be assumed a desk can be created in
- * all devices / form-factors.
- */
- fun getOrCreateDesk(displayId: Int, deskId: Int): Desk
+ /** Creates a desk record. */
+ fun createDesk(displayId: Int, deskId: Int)
/** Returns the desk with the given id, or null if it does not exist. */
fun getDesk(deskId: Int): Desk?
@@ -894,7 +906,8 @@ class DesktopRepository(
/**
* A [DesktopData] implementation that only supports one desk per display.
*
- * Internally, it reuses the displayId as that display's single desk's id.
+ * Internally, it reuses the displayId as that display's single desk's id. It also never truly
+ * "removes" a desk, it just clears its content.
*/
private class SingleDesktopData : DesktopData {
private val deskByDisplayId =
@@ -907,12 +920,13 @@ class DesktopRepository(
}
}
- override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk {
- check(displayId == deskId)
- return deskByDisplayId.getOrCreate(displayId)
+ override fun createDesk(displayId: Int, deskId: Int) {
+ check(displayId == deskId) { "Display and desk ids must match" }
+ deskByDisplayId.getOrCreate(displayId)
}
- override fun getDesk(deskId: Int): Desk = getOrCreateDesk(deskId, deskId)
+ override fun getDesk(deskId: Int): Desk =
+ checkNotNull(deskByDisplayId[deskId]) { "Expected desk $deskId to exist" }
override fun getActiveDesk(displayId: Int): Desk {
// TODO: 389787966 - consider migrating to an "active" state instead of checking the
@@ -927,7 +941,7 @@ class DesktopRepository(
// existence of visible desktop windows, among other factors.
}
- override fun getDefaultDesk(displayId: Int): Desk = getOrCreateDesk(displayId, displayId)
+ override fun getDefaultDesk(displayId: Int): Desk = getDesk(deskId = displayId)
override fun getAllActiveDesks(): Set<Desk> =
deskByDisplayId.valueIterator().asSequence().toSet()
@@ -943,7 +957,7 @@ class DesktopRepository(
}
override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) {
- consumer(getOrCreateDesk(displayId, displayId))
+ consumer(getDesk(deskId = displayId))
}
override fun desksSequence(): Sequence<Desk> = deskByDisplayId.valueIterator().asSequence()
@@ -962,16 +976,14 @@ class DesktopRepository(
private class MultiDesktopData : DesktopData {
private val desktopDisplays = SparseArray<DesktopDisplay>()
- override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk {
+ override fun createDesk(displayId: Int, deskId: Int) {
val display =
desktopDisplays[displayId]
?: DesktopDisplay(displayId).also { desktopDisplays[displayId] = it }
- val desk =
- display.orderedDesks.find { desk -> desk.deskId == deskId }
- ?: Desk(deskId = deskId, displayId = displayId).also {
- display.orderedDesks.add(it)
- }
- return desk
+ check(display.orderedDesks.none { desk -> desk.deskId == deskId }) {
+ "Attempting to create desk#$deskId that already exists in display#$displayId"
+ }
+ display.orderedDesks.add(Desk(deskId = deskId, displayId = displayId))
}
override fun getDesk(deskId: Int): Desk? {
@@ -999,7 +1011,8 @@ class DesktopRepository(
override fun getDefaultDesk(displayId: Int): Desk? {
val display = desktopDisplays[displayId] ?: return null
- return display.orderedDesks.firstOrNull()
+ return display.orderedDesks.find { it.deskId == display.activeDeskId }
+ ?: display.orderedDesks.firstOrNull()
}
override fun getAllActiveDesks(): Set<Desk> {
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 3ae553596631..6c57dc7056a6 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
@@ -102,6 +102,8 @@ import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCR
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
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
@@ -180,6 +182,7 @@ class DesktopTasksController(
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
private val bubbleController: Optional<BubbleController>,
private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
+ private val desksOrganizer: DesksOrganizer,
) :
RemoteCallable<DesktopTasksController>,
Transitions.TransitionHandler,
@@ -232,6 +235,9 @@ class DesktopTasksController(
// Used to prevent handleRequest from moving the new fullscreen task to freeform.
private var dragAndDropFullscreenCookie: Binder? = null
+ // A listener that is invoked after a desk has been remove from the system. */
+ var onDeskRemovedListener: OnDeskRemovedListener? = null
+
init {
desktopMode = DesktopModeImpl()
if (DesktopModeStatus.canEnterDesktopMode(context)) {
@@ -415,6 +421,18 @@ class DesktopTasksController(
return isFreeformDisplay
}
+ /** Creates a new desk in the given display. */
+ fun createDesk(displayId: Int) {
+ if (Flags.enableMultipleDesktopsBackend()) {
+ desksOrganizer.createDesk(displayId) { deskId ->
+ taskRepository.addDesk(displayId = displayId, deskId = deskId)
+ }
+ } else {
+ // In single-desk, the desk reuses the display id.
+ taskRepository.addDesk(displayId = displayId, deskId = displayId)
+ }
+ }
+
/** Moves task to desktop mode if task is running, else launches it in desktop mode. */
@JvmOverloads
fun moveTaskToDesktop(
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
new file mode 100644
index 000000000000..5cbb59fbf323
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+import android.app.ActivityManager
+import android.window.TransitionInfo
+import android.window.WindowContainerTransaction
+
+/** An organizer of desk containers in which to host child desktop windows. */
+interface DesksOrganizer {
+ /** Creates a new desk container in the given display. */
+ fun createDesk(displayId: Int, callback: OnCreateCallback)
+
+ /** Activates the given desk, making it visible in its display. */
+ fun activateDesk(wct: WindowContainerTransaction, deskId: Int)
+
+ /** Removes the given desk and its desktop windows. */
+ fun removeDesk(wct: WindowContainerTransaction, deskId: Int)
+
+ /** Moves the given task to the given desk. */
+ fun moveTaskToDesk(
+ wct: WindowContainerTransaction,
+ deskId: Int,
+ task: ActivityManager.RunningTaskInfo,
+ )
+
+ /**
+ * Returns the desk id in which the task in the given change is located at the end of a
+ * transition, if any.
+ */
+ fun getDeskAtEnd(change: TransitionInfo.Change): Int?
+
+ /** A callback that is invoked when the desk container is created. */
+ fun interface OnCreateCallback {
+ /** Calls back when the [deskId] has been created. */
+ fun onCreated(deskId: Int)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/OnDeskRemovedListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/OnDeskRemovedListener.kt
new file mode 100644
index 000000000000..452ddb1ff8fb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/OnDeskRemovedListener.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+/** A listener for removals of desks. */
+fun interface OnDeskRemovedListener {
+ /** Called when a desk has been removed from the system. */
+ fun onDeskRemoved(lastDisplayId: Int, deskId: Int)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
new file mode 100644
index 000000000000..79c48c5e9594
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.util.SparseArray
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import android.window.WindowContainerTransaction
+import androidx.core.util.forEach
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer.OnCreateCallback
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellInit
+import java.io.PrintWriter
+
+/** A [DesksOrganizer] that uses root tasks as the container of each desk. */
+class RootTaskDesksOrganizer(
+ shellInit: ShellInit,
+ shellCommandHandler: ShellCommandHandler,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+) : DesksOrganizer, ShellTaskOrganizer.TaskListener {
+
+ private val deskCreateRequests = mutableListOf<CreateRequest>()
+ @VisibleForTesting val roots = SparseArray<DeskRoot>()
+
+ init {
+ if (Flags.enableMultipleDesktopsBackend()) {
+ shellInit.addInitCallback(
+ { shellCommandHandler.addDumpCallback(this::dump, this) },
+ this,
+ )
+ }
+ }
+
+ override fun createDesk(displayId: Int, callback: OnCreateCallback) {
+ logV("createDesk in display: %d", displayId)
+ deskCreateRequests += CreateRequest(displayId, callback)
+ shellTaskOrganizer.createRootTask(
+ displayId,
+ WINDOWING_MODE_FREEFORM,
+ /* listener = */ this,
+ /* removeWithTaskOrganizer = */ true,
+ )
+ }
+
+ override fun removeDesk(wct: WindowContainerTransaction, deskId: Int) {
+ logV("removeDesk %d", deskId)
+ val desk = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" }
+ wct.removeRootTask(desk.taskInfo.token)
+ }
+
+ override fun activateDesk(wct: WindowContainerTransaction, deskId: Int) {
+ logV("activateDesk %d", deskId)
+ val root = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" }
+ wct.reorder(root.taskInfo.token, /* onTop= */ true)
+ wct.setLaunchRoot(
+ /* container= */ root.taskInfo.token,
+ /* windowingModes= */ intArrayOf(WINDOWING_MODE_FREEFORM, WINDOWING_MODE_UNDEFINED),
+ /* activityTypes= */ intArrayOf(ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD),
+ )
+ }
+
+ override fun moveTaskToDesk(
+ wct: WindowContainerTransaction,
+ deskId: Int,
+ task: RunningTaskInfo,
+ ) {
+ val root = roots[deskId] ?: error("Root not found for desk: $deskId")
+ wct.reparent(task.token, root.taskInfo.token, /* onTop= */ true)
+ }
+
+ override fun getDeskAtEnd(change: TransitionInfo.Change): Int? =
+ change.taskInfo?.parentTaskId?.takeIf { it in roots }
+
+ override fun onTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) {
+ if (taskInfo.parentTaskId in roots) {
+ val deskId = taskInfo.parentTaskId
+ val taskId = taskInfo.taskId
+ logV("Task #$taskId appeared in desk #$deskId")
+ addChildToDesk(taskId = taskId, deskId = deskId)
+ return
+ }
+ val deskId = taskInfo.taskId
+ check(deskId !in roots) { "A root already exists for desk: $deskId" }
+ val request =
+ checkNotNull(deskCreateRequests.firstOrNull { it.displayId == taskInfo.displayId }) {
+ "Task ${taskInfo.taskId} appeared without pending create request"
+ }
+ logV("Desk #$deskId appeared")
+ roots[deskId] = DeskRoot(deskId, taskInfo, leash)
+ deskCreateRequests.remove(request)
+ request.onCreateCallback.onCreated(deskId)
+ }
+
+ override fun onTaskInfoChanged(taskInfo: RunningTaskInfo) {
+ if (roots.contains(taskInfo.taskId)) {
+ val deskId = taskInfo.taskId
+ roots[deskId] = roots[deskId].copy(taskInfo = taskInfo)
+ }
+ }
+
+ override fun onTaskVanished(taskInfo: RunningTaskInfo) {
+ if (roots.contains(taskInfo.taskId)) {
+ val deskId = taskInfo.taskId
+ val deskRoot = roots[deskId]
+ // Use the last saved taskInfo to obtain the displayId. Using the local one here will
+ // return -1 since the task is not unassociated with a display.
+ val displayId = deskRoot.taskInfo.displayId
+ logV("Desk #$deskId vanished from display #$displayId")
+ roots.remove(deskId)
+ return
+ }
+ // At this point, [parentTaskId] may be unset even if this is a task vanishing from a desk,
+ // so search through each root to remove this if it's a child.
+ roots.forEach { deskId, deskRoot ->
+ if (deskRoot.children.remove(taskInfo.taskId)) {
+ logV("Task #${taskInfo.taskId} vanished from desk #$deskId")
+ return
+ }
+ }
+ }
+
+ @VisibleForTesting
+ data class DeskRoot(
+ val deskId: Int,
+ val taskInfo: RunningTaskInfo,
+ val leash: SurfaceControl,
+ val children: MutableSet<Int> = mutableSetOf(),
+ )
+
+ override fun dump(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ pw.println("$prefix$TAG")
+ pw.println("${innerPrefix}Desk Roots:")
+ roots.forEach { deskId, root ->
+ pw.println("$innerPrefix #$deskId visible=${root.taskInfo.isVisible}")
+ pw.println("$innerPrefix children=${root.children}")
+ }
+ }
+
+ private fun addChildToDesk(taskId: Int, deskId: Int) {
+ roots.forEach { _, deskRoot ->
+ if (deskRoot.deskId == deskId) {
+ deskRoot.children.add(taskId)
+ } else {
+ deskRoot.children.remove(taskId)
+ }
+ }
+ }
+
+ private data class CreateRequest(val displayId: Int, val onCreateCallback: OnCreateCallback)
+
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ companion object {
+ private const val TAG = "RootTaskDesksOrganizer"
+ }
+}
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 58a49a035bb6..5a89451ffdbc 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
@@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode.persistence
import android.content.Context
import android.window.DesktopModeFlags
+import com.android.window.flags.Flags
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -54,10 +55,22 @@ class DesktopRepositoryInitializerImpl(
DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
?: persistentDesktop.zOrderedTasksCount
var visibleTasksCount = 0
+ repository.addDesk(
+ displayId = persistentDesktop.displayId,
+ deskId =
+ if (Flags.enableMultipleDesktopsBackend()) {
+ persistentDesktop.desktopId
+ } else {
+ // When disabled, desk ids are always the display id.
+ persistentDesktop.displayId
+ },
+ )
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 &&
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index ecad5217b87f..957fdf995776 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -183,6 +183,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_resizeable_doNothing() {
+ userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask()
taskStackListener.onActivityRequestedOrientationChanged(
@@ -195,6 +197,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_nonResizeableFullscreen_doNothing() {
+ userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = createFullscreenTask()
task.isResizeable = false
val activityInfo = ActivityInfo()
@@ -214,6 +218,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_nonResizeablePortrait_requestSameOrientation_doNothing() {
+ userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask(isResizeable = false)
val newTask =
setUpFreeformTask(
@@ -228,6 +234,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_notInDesktopMode_doNothing() {
+ userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask(isResizeable = false)
userRepositories.current.updateTask(task.displayId, task.taskId, isVisible = false)
@@ -241,6 +249,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_nonResizeablePortrait_respectLandscapeRequest() {
+ userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask(isResizeable = false)
val oldBounds = task.configuration.windowConfiguration.bounds
val newTask =
@@ -263,6 +273,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_nonResizeableLandscape_respectPortraitRequest() {
+ userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val oldBounds = Rect(0, 0, 500, 200)
val task =
setUpFreeformTask(
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 6a3717427e93..5d8d5a716504 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
@@ -20,6 +20,8 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.ContentResolver
import android.os.Binder
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings
import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
import android.testing.AndroidTestingRunner
@@ -29,20 +31,25 @@ import android.view.WindowManager.TRANSIT_CHANGE
import android.window.DisplayAreaInfo
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags
import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
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.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito.anyInt
@@ -53,6 +60,7 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
/**
* Test class for [DesktopDisplayEventHandler]
@@ -63,18 +71,33 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
class DesktopDisplayEventHandlerTest : ShellTestCase() {
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var transitions: Transitions
@Mock lateinit var displayController: DisplayController
@Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var mockWindowManager: IWindowManager
+ @Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories
+ @Mock private lateinit var mockDesktopRepository: DesktopRepository
+ @Mock private lateinit var mockDesktopTasksController: DesktopTasksController
+ private lateinit var mockitoSession: StaticMockitoSession
private lateinit var shellInit: ShellInit
private lateinit var handler: DesktopDisplayEventHandler
+ private val onDisplaysChangedListenerCaptor = argumentCaptor<OnDisplaysChangedListener>()
+
@Before
fun setUp() {
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+
shellInit = spy(ShellInit(testExecutor))
+ whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
@@ -86,8 +109,17 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
displayController,
rootTaskDisplayAreaOrganizer,
mockWindowManager,
+ mockDesktopUserRepositories,
+ mockDesktopTasksController,
)
shellInit.init()
+ verify(displayController)
+ .addDisplayWindowListener(onDisplaysChangedListenerCaptor.capture())
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
}
private fun testDisplayWindowingModeSwitch(
@@ -96,8 +128,6 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
expectTransition: Boolean,
) {
val externalDisplayId = 100
- val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java)
- verify(displayController).addDisplayWindowListener(captor.capture())
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode
whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode }
@@ -111,12 +141,12 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
// The external display connected.
whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
.thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
- captor.value.onDisplayAdded(externalDisplayId)
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId)
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
// The external display disconnected.
whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
.thenReturn(intArrayOf(DEFAULT_DISPLAY))
- captor.value.onDisplayRemoved(externalDisplayId)
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId)
if (expectTransition) {
val arg = argumentCaptor<WindowContainerTransaction>()
@@ -159,6 +189,44 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
)
}
+ @Test
+ fun testDisplayAdded_supportsDesks_createsDesk() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
+
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+
+ verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun testDisplayAdded_cannotEnterDesktopMode_doesNotCreateDesk() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
+
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+
+ verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testDeskRemoved_noDesksRemain_createsDesk() {
+ whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(0)
+
+ handler.onDeskRemoved(DEFAULT_DISPLAY, deskId = 1)
+
+ verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testDeskRemoved_desksRemain_doesNotCreateDesk() {
+ whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(1)
+
+ handler.onDeskRemoved(DEFAULT_DISPLAY, deskId = 1)
+
+ verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
+ }
+
private class ExtendedDisplaySettingsSession(
private val contentResolver: ContentResolver,
private val overrideValue: Int,
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 8d73f3f59afd..5736793cd6a9 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
@@ -18,12 +18,13 @@ package com.android.wm.shell.desktopmode
import android.graphics.Rect
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.platform.test.flag.junit.SetFlagsRule
-import android.testing.AndroidTestingRunner
import android.util.ArraySet
import android.view.Display.DEFAULT_DISPLAY
import android.view.Display.INVALID_DISPLAY
import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.wm.shell.ShellTestCase
@@ -56,6 +57,8 @@ import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
/**
* Tests for [@link DesktopRepository].
@@ -63,11 +66,11 @@ import org.mockito.kotlin.whenever
* Build/Install/Run: atest WMShellUnitTests:DesktopRepositoryTest
*/
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@ExperimentalCoroutinesApi
-class DesktopRepositoryTest : ShellTestCase() {
+class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
+ @JvmField @Rule val setFlagsRule = SetFlagsRule(flags)
private lateinit var repo: DesktopRepository
private lateinit var shellInit: ShellInit
@@ -86,6 +89,8 @@ class DesktopRepositoryTest : ShellTestCase() {
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) })
.thenReturn(Desktop.getDefaultInstance())
shellInit.init()
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DESKTOP_ID)
+ repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DESKTOP_ID)
}
@After
@@ -137,6 +142,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun addTask_multipleDisplays_notifiesCorrectListener() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val listener = TestListener()
repo.addActiveTaskListener(listener)
@@ -150,6 +156,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun addTask_multipleDisplays_moveToAnotherDisplay() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.addTask(SECOND_DISPLAY, taskId = 1, isVisible = true)
assertThat(repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)).isEmpty()
@@ -310,19 +317,21 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun isOnlyVisibleNonClosingTask_multipleDisplays() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
repo.updateTask(SECOND_DISPLAY, taskId = 3, isVisible = true)
// Not the only task on DEFAULT_DISPLAY
assertThat(repo.isVisibleTask(1)).isTrue()
- assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse()
+ assertThat(repo.isOnlyVisibleNonClosingTask(1, DEFAULT_DISPLAY)).isFalse()
// Not the only task on DEFAULT_DISPLAY
assertThat(repo.isVisibleTask(2)).isTrue()
- assertThat(repo.isOnlyVisibleNonClosingTask(2)).isFalse()
+ assertThat(repo.isOnlyVisibleNonClosingTask(2, DEFAULT_DISPLAY)).isFalse()
// The only visible task on SECOND_DISPLAY
assertThat(repo.isVisibleTask(3)).isTrue()
- assertThat(repo.isOnlyVisibleNonClosingTask(3)).isTrue()
+ assertThat(repo.isOnlyVisibleNonClosingTask(3, SECOND_DISPLAY)).isTrue()
// Not a visible task
assertThat(repo.isVisibleTask(99)).isFalse()
assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse()
@@ -343,6 +352,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun addListener_tasksOnDifferentDisplay_doesNotNotify() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.updateTask(SECOND_DISPLAY, taskId = 1, isVisible = true)
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
@@ -351,7 +361,7 @@ class DesktopRepositoryTest : ShellTestCase() {
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
// One call as adding listener notifies it
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(0)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
}
@Test
@@ -365,11 +375,14 @@ class DesktopRepositoryTest : ShellTestCase() {
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
+ // 1 from registration, 2 for the updates.
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
}
@Test
fun updateTask_visibleTask_addVisibleTaskNotifiesListenerForThatDisplay() {
+ repo.addDesk(displayId = 1, deskId = 1)
+ repo.setActiveDesk(displayId = 1, deskId = 1)
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
@@ -378,22 +391,27 @@ class DesktopRepositoryTest : ShellTestCase() {
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+ // 1 for the registration, 1 for the update.
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(0)
- assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(0)
+ // 1 for the registration.
+ assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
repo.updateTask(displayId = 1, taskId = 2, isVisible = true)
executor.flushAll()
// Listener for secondary display is notified
assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1)
- assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
+ // 1 for the registration, 1 for the update.
+ assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(2)
// No changes to listener for default display
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
}
@Test
fun updateTask_taskOnDefaultBecomesVisibleOnSecondDisplay_listenersNotified() {
+ repo.addDesk(displayId = 1, deskId = 1)
+ repo.setActiveDesk(displayId = 1, deskId = 1)
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
@@ -406,14 +424,15 @@ class DesktopRepositoryTest : ShellTestCase() {
repo.updateTask(displayId = 1, taskId = 1, isVisible = true)
executor.flushAll()
- // Default display should have 2 calls
- // 1 - visible task added
- // 2 - visible task removed
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2)
+ // Default display should have 3 calls
+ // 1 - listener registered
+ // 2 - visible task added
+ // 3 - visible task removed
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
- // Secondary display should have 1 call for visible task added
- assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
+ // Secondary display should have 2 calls for registration + visible task added
+ assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(2)
assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1)
}
@@ -431,13 +450,13 @@ class DesktopRepositoryTest : ShellTestCase() {
repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = false)
executor.flushAll()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4)
repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = false)
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0)
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4)
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(5)
}
/**
@@ -458,7 +477,8 @@ class DesktopRepositoryTest : ShellTestCase() {
repo.updateTask(INVALID_DISPLAY, taskId = 1, isVisible = false)
executor.flushAll()
- assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
+ // 1 from registering, 1x3 for each update including the one to the invalid display.
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4)
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
}
@@ -497,6 +517,8 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun getVisibleTaskCount_multipleDisplays_returnsCorrectCount() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
@@ -674,8 +696,6 @@ class DesktopRepositoryTest : ShellTestCase() {
repo.removeTask(INVALID_DISPLAY, taskId = 1)
- val invalidDisplayTasks = repo.getFreeformTasksInZOrder(INVALID_DISPLAY)
- assertThat(invalidDisplayTasks).isEmpty()
val validDisplayTasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
assertThat(validDisplayTasks).isEmpty()
}
@@ -746,6 +766,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeTask_validDisplay_differentDisplay_doesNotRemovesTask() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeTask(SECOND_DISPLAY, taskId = 1)
@@ -758,6 +779,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
fun removeTask_validDisplayButDifferentDisplay_persistenceEnabled_doesNotRemoveTask() {
runTest(StandardTestDispatcher()) {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.removeTask(SECOND_DISPLAY, taskId = 1)
@@ -784,10 +806,10 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeTask_removesTaskBoundsBeforeMaximize() {
val taskId = 1
- repo.addTask(THIRD_DISPLAY, taskId, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true)
repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200))
- repo.removeTask(THIRD_DISPLAY, taskId)
+ repo.removeTask(DEFAULT_DISPLAY, taskId)
assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull()
}
@@ -795,16 +817,17 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeTask_removesTaskBoundsBeforeImmersive() {
val taskId = 1
- repo.addTask(THIRD_DISPLAY, taskId, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true)
repo.saveBoundsBeforeFullImmersive(taskId, Rect(0, 0, 200, 200))
- repo.removeTask(THIRD_DISPLAY, taskId)
+ repo.removeTask(DEFAULT_DISPLAY, taskId)
assertThat(repo.removeBoundsBeforeFullImmersive(taskId)).isNull()
}
@Test
fun removeTask_removesActiveTask() {
+ repo.addDesk(THIRD_DISPLAY, THIRD_DISPLAY)
val taskId = 1
val listener = TestListener()
repo.addActiveTaskListener(listener)
@@ -829,6 +852,7 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun removeTask_updatesTaskVisibility() {
+ repo.addDesk(displayId = THIRD_DISPLAY, deskId = THIRD_DISPLAY)
val taskId = 1
repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true)
@@ -930,8 +954,8 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun updateTask_minimizedTaskBecomesVisible_unminimizesTask() {
- repo.minimizeTask(displayId = 10, taskId = 2)
- repo.updateTask(displayId = 10, taskId = 2, isVisible = true)
+ repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
+ repo.updateTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
val isMinimizedTask = repo.isMinimizedTask(taskId = 2)
@@ -1003,34 +1027,34 @@ class DesktopRepositoryTest : ShellTestCase() {
fun setTaskInFullImmersiveState_savedAsInImmersiveState() {
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse()
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true)
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue()
}
@Test
fun removeTaskInFullImmersiveState_removedAsInImmersiveState() {
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true)
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue()
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = false)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = false)
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse()
}
@Test
fun removeTaskInFullImmersiveState_otherWasImmersive_otherRemainsImmersive() {
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true)
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 2, immersive = false)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 2, immersive = false)
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue()
}
@Test
fun setTaskInFullImmersiveState_sameDisplay_overridesExistingFullImmersiveTask() {
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 2, immersive = true)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 2, immersive = true)
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse()
assertThat(repo.isTaskInFullImmersiveState(taskId = 2)).isTrue()
@@ -1038,8 +1062,10 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun setTaskInFullImmersiveState_differentDisplay_bothAreImmersive() {
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1, taskId = 2, immersive = true)
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true)
+ repo.setTaskInFullImmersiveState(SECOND_DISPLAY, taskId = 2, immersive = true)
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue()
assertThat(repo.isTaskInFullImmersiveState(taskId = 2)).isTrue()
@@ -1061,11 +1087,13 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun getTaskInFullImmersiveState_byDisplay() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true)
- repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1, taskId = 2, immersive = true)
+ repo.setTaskInFullImmersiveState(SECOND_DISPLAY, taskId = 2, immersive = true)
assertThat(repo.getTaskInFullImmersiveState(DEFAULT_DESKTOP_ID)).isEqualTo(1)
- assertThat(repo.getTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1)).isEqualTo(2)
+ assertThat(repo.getTaskInFullImmersiveState(SECOND_DISPLAY)).isEqualTo(2)
}
@Test
@@ -1089,11 +1117,13 @@ class DesktopRepositoryTest : ShellTestCase() {
@Test
fun setTaskInPip_multipleDisplays_bothAreInPip() {
+ repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- repo.setTaskInPip(DEFAULT_DESKTOP_ID + 1, taskId = 2, enterPip = true)
+ repo.setTaskInPip(SECOND_DISPLAY, taskId = 2, enterPip = true)
assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID + 1, taskId = 2)).isTrue()
+ assertThat(repo.isTaskMinimizedPipInDisplay(SECOND_DISPLAY, taskId = 2)).isTrue()
}
@Test
@@ -1169,5 +1199,10 @@ class DesktopRepositoryTest : ShellTestCase() {
const val THIRD_DISPLAY = 345
private const val DEFAULT_USER_ID = 1000
private const val DEFAULT_DESKTOP_ID = 0
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> =
+ FlagsParameterization.allCombinationsOf(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
}
}
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 fffaab36c9ad..0c1fd732f23e 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
@@ -48,8 +48,8 @@ import android.os.IBinder
import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.platform.test.flag.junit.SetFlagsRule
-import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.Gravity
@@ -117,6 +117,7 @@ import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCR
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
@@ -184,6 +185,8 @@ import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
/**
* Test class for {@link DesktopTasksController}
@@ -191,12 +194,12 @@ import org.mockito.quality.Strictness
* Usage: atest WMShellUnitTests:DesktopTasksControllerTest
*/
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@ExperimentalCoroutinesApi
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-class DesktopTasksControllerTest : ShellTestCase() {
+class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
+ @JvmField @Rule val setFlagsRule = SetFlagsRule(flags)
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var shellCommandHandler: ShellCommandHandler
@@ -247,6 +250,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
DesktopWallpaperActivityTokenProvider
@Mock
private lateinit var overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver
+ @Mock private lateinit var desksOrganizer: DesksOrganizer
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
@@ -358,6 +362,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
taskRepository = userRepositories.current
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY)
}
private fun createController() =
@@ -395,6 +401,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
desktopWallpaperActivityTokenProvider,
Optional.of(bubbleController),
overviewToDesktopTransitionObserver,
+ desksOrganizer,
)
@After
@@ -613,7 +620,12 @@ class DesktopTasksControllerTest : ShellTestCase() {
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
)
+ @DisableFlags(
+ /** TODO: b/362720497 - re-enable when activation is implemented. */
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
+ )
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_shouldShowWallpaper() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTask = setUpHomeTask(SECOND_DISPLAY)
val task1 = setUpFreeformTask(SECOND_DISPLAY)
val task2 = setUpFreeformTask(SECOND_DISPLAY)
@@ -634,8 +646,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ /** TODO: b/362720497 - re-enable when activation is implemented. */
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTask = setUpHomeTask(SECOND_DISPLAY)
val task1 = setUpFreeformTask(SECOND_DISPLAY)
val task2 = setUpFreeformTask(SECOND_DISPLAY)
@@ -674,8 +691,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ /** TODO: b/362720497 - re-enable when activation is implemented. */
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTask = setUpHomeTask(SECOND_DISPLAY)
val task1 = setUpFreeformTask(SECOND_DISPLAY)
val task2 = setUpFreeformTask(SECOND_DISPLAY)
@@ -777,6 +799,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
setUpHomeTask(SECOND_DISPLAY)
@@ -797,6 +820,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
setUpHomeTask(SECOND_DISPLAY)
@@ -883,6 +907,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun visibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
setUpHomeTask()
setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible)
setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible)
@@ -1476,6 +1502,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
markTaskHidden(freeformTaskDefault)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY)
val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
markTaskHidden(freeformTaskSecond)
@@ -1673,6 +1700,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() {
val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
controller.moveToFullscreen(taskDefaultDisplay.taskId, transitionSource = UNKNOWN)
@@ -1853,6 +1881,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun moveToNextDisplay_moveFromFirstToSecondDisplay() {
// Set up two display ids
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
// Create a mock for the target display area: second display
@@ -1882,6 +1911,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
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)
@@ -1901,6 +1931,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
)
fun moveToNextDisplay_wallpaperOnSystemUser_reorderWallpaperToBack() {
// Set up two display ids
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
// Create a mock for the target display area: second display
@@ -1925,6 +1956,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun moveToNextDisplay_wallpaperNotOnSystemUser_removeWallpaper() {
// Set up two display ids
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
// Create a mock for the target display area: second display
@@ -2049,6 +2081,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
fun moveToNextDisplay_defaultBoundsWhenDestinationTooSmall() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
// Set up two display ids
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
@@ -2090,6 +2123,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
)
fun moveToNextDisplay_destinationGainGlobalFocus() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
// Set up two display ids
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
@@ -3158,6 +3192,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_closeTransition_singleTaskNoToken_secondaryDisplay_launchesHome() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
@@ -4933,7 +4969,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_notShowingDesktop_doesNotPlay() {
- val triggerTask = setUpFullscreenTask(displayId = 5)
+ val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
taskRepository.setTaskInFullImmersiveState(
displayId = triggerTask.displayId,
taskId = triggerTask.taskId,
@@ -4951,7 +4987,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_notOpening_doesNotPlay() {
- val triggerTask = setUpFreeformTask(displayId = 5)
+ val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
taskRepository.setTaskInFullImmersiveState(
displayId = triggerTask.displayId,
taskId = triggerTask.taskId,
@@ -4969,7 +5005,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_notImmersive_doesNotPlay() {
- val triggerTask = setUpFreeformTask(displayId = 5)
+ val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
taskRepository.setTaskInFullImmersiveState(
displayId = triggerTask.displayId,
taskId = triggerTask.taskId,
@@ -4988,8 +5024,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_fullscreenEntersDesktop_plays() {
// At least one freeform task to be in a desktop.
- val existingTask = setUpFreeformTask(displayId = 5)
- val triggerTask = setUpFullscreenTask(displayId = 5)
+ val existingTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue()
taskRepository.setTaskInFullImmersiveState(
displayId = existingTask.displayId,
@@ -5008,7 +5044,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_fullscreenStaysFullscreen_doesNotPlay() {
- val triggerTask = setUpFullscreenTask(displayId = 5)
+ val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse()
assertThat(
@@ -5023,8 +5059,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_freeformStaysInDesktop_plays() {
// At least one freeform task to be in a desktop.
- val existingTask = setUpFreeformTask(displayId = 5)
- val triggerTask = setUpFreeformTask(displayId = 5, active = false)
+ val existingTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, active = false)
assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue()
taskRepository.setTaskInFullImmersiveState(
displayId = existingTask.displayId,
@@ -5043,7 +5079,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun shouldPlayDesktopAnimation_freeformExitsDesktop_doesNotPlay() {
- val triggerTask = setUpFreeformTask(displayId = 5, active = false)
+ val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, active = false)
assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse()
assertThat(
@@ -5054,6 +5090,19 @@ class DesktopTasksControllerTest : ShellTestCase() {
.isFalse()
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testCreateDesk() {
+ val currentDeskCount = taskRepository.getNumberOfDesks(DEFAULT_DISPLAY)
+ whenever(desksOrganizer.createDesk(eq(DEFAULT_DISPLAY), any())).thenAnswer { invocation ->
+ (invocation.arguments[1] as DesksOrganizer.OnCreateCallback).onCreated(deskId = 5)
+ }
+
+ controller.createDesk(DEFAULT_DISPLAY)
+
+ assertThat(taskRepository.getNumberOfDesks(DEFAULT_DISPLAY)).isEqualTo(currentDeskCount + 1)
+ }
+
private class RunOnStartTransitionCallback : ((IBinder) -> Unit) {
var invocations = 0
private set
@@ -5387,6 +5436,11 @@ class DesktopTasksControllerTest : ShellTestCase() {
val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
const val MAX_TASK_LIMIT = 6
private const val TASKBAR_FRAME_HEIGHT = 200
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> =
+ FlagsParameterization.allCombinationsOf(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index e85901bbd9d4..554b09f130bd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -180,6 +180,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun addPendingMinimizeTransition_taskIsNotMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask()
markTaskHidden(task)
@@ -190,6 +192,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_noPendingTransition_taskIsNotMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask()
markTaskHidden(task)
@@ -203,6 +207,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_differentPendingTransition_taskIsNotMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val pendingTransition = Binder()
val taskTransition = Binder()
val task = setUpFreeformTask()
@@ -219,6 +225,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_pendingTransition_noTaskChange_taskVisible_taskIsNotMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
markTaskVisible(task)
@@ -232,6 +240,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_pendingTransition_noTaskChange_taskInvisible_taskIsMinimized() {
val transition = Binder()
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask()
markTaskHidden(task)
addPendingMinimizeChange(transition, taskId = task.taskId)
@@ -243,6 +253,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_pendingTransition_changeTaskToBack_taskIsMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
addPendingMinimizeChange(transition, taskId = task.taskId)
@@ -257,6 +269,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_pendingTransition_changeTaskToBack_boundsSaved() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val bounds = Rect(0, 0, 200, 200)
val transition = Binder()
val task = setUpFreeformTask()
@@ -280,6 +294,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_transitionMergedFromPending_taskIsMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val mergedTransition = Binder()
val newTransition = Binder()
val task = setUpFreeformTask()
@@ -302,6 +318,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
desktopTaskRepo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
desktopTaskRepo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
@@ -318,6 +336,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val wct = WindowContainerTransaction()
desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
DEFAULT_DISPLAY,
@@ -330,6 +350,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_removesAllMinimizedTasks() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
@@ -351,6 +373,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_backNavEnabled_doesNothing() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
@@ -364,6 +388,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun addAndGetMinimizeTaskChanges_tasksWithinLimit_noTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val wct = WindowContainerTransaction()
@@ -380,6 +406,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun addAndGetMinimizeTaskChanges_tasksAboveLimit_backTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
// The following list will be ordered bottom -> top, as the last task is moved to top last.
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
@@ -399,6 +427,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_noTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = tasks[0].taskId)
@@ -416,6 +446,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_tasksWithinLimit_returnsNull() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
val minimizedTask =
@@ -426,6 +458,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_tasksAboveLimit_returnsBackTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val tasks = (1..MAX_TASK_LIMIT + 1).map { setUpFreeformTask() }
val minimizedTask =
@@ -437,6 +471,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_tasksAboveLimit_otherLimit_returnsBackTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
desktopTasksLimiter =
DesktopTasksLimiter(
transitions,
@@ -458,6 +494,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_withNewTask_tasksAboveLimit_returnsBackTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
val minimizedTask =
@@ -472,6 +510,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_tasksAtLimit_newIntentReturnsBackTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
val minimizedTask =
desktopTasksLimiter.getTaskIdToMinimize(
@@ -486,6 +526,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun minimizeTransitionReadyAndFinished_logsJankInstrumentationBeginAndEnd() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val transition = Binder()
val task = setUpFreeformTask()
@@ -510,6 +552,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun minimizeTransitionReadyAndAborted_logsJankInstrumentationBeginAndCancel() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val transition = Binder()
val task = setUpFreeformTask()
@@ -534,6 +578,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun minimizeTransitionReadyAndMerged_logsJankInstrumentationBeginAndEnd() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
(1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
val mergedTransition = Binder()
val newTransition = Binder()
@@ -566,6 +612,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getMinimizingTask_pendingTaskTransition_returnsTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
addPendingMinimizeChange(
@@ -582,6 +630,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getMinimizingTask_activeTaskTransition_returnsTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
addPendingMinimizeChange(
@@ -613,6 +663,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getUnminimizingTask_pendingTaskTransition_returnsTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
addPendingUnminimizeChange(
@@ -632,6 +684,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getUnminimizingTask_activeTaskTransition_returnsTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
addPendingMinimizeChange(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index aee8821a63f6..5f8991e81968 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -35,6 +35,7 @@ object DesktopTestHelpers {
): RunningTaskInfo =
TestRunningTaskInfoBuilder()
.setDisplayId(displayId)
+ .setParentTaskId(displayId)
.setToken(MockToken().token())
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
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
new file mode 100644
index 000000000000..a07203d86b75
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellInit
+import com.google.common.truth.Truth.assertThat
+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.kotlin.mock
+
+/**
+ * Tests for [RootTaskDesksOrganizer].
+ *
+ * Usage: atest WMShellUnitTests:RootTaskDesksOrganizerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RootTaskDesksOrganizerTest : ShellTestCase() {
+
+ private val testExecutor = TestShellExecutor()
+ private val testShellInit = ShellInit(testExecutor)
+ private val mockShellCommandHandler = mock<ShellCommandHandler>()
+ private val mockShellTaskOrganizer = mock<ShellTaskOrganizer>()
+
+ private lateinit var organizer: RootTaskDesksOrganizer
+
+ @Before
+ fun setUp() {
+ organizer =
+ RootTaskDesksOrganizer(testShellInit, mockShellCommandHandler, mockShellTaskOrganizer)
+ }
+
+ @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 testOnTaskAppeared_withoutRequest_throws() {
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+
+ assertThrows(Exception::class.java) {
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ }
+ }
+
+ @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())
+
+ assertThrows(Exception::class.java) {
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ }
+ }
+
+ @Test
+ fun testOnTaskVanished_removesRoot() {
+ val callback = FakeOnCreateCallback()
+ organizer.createDesk(Display.DEFAULT_DISPLAY, callback)
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ organizer.onTaskVanished(freeformRoot)
+
+ assertThat(organizer.roots.contains(freeformRoot.taskId)).isFalse()
+ }
+
+ @Test
+ fun testDesktopWindowAppearsInDesk() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ val child = createFreeformTask().apply { parentTaskId = freeformRoot.taskId }
+
+ organizer.onTaskAppeared(child, SurfaceControl())
+
+ assertThat(organizer.roots[freeformRoot.taskId].children).contains(child.taskId)
+ }
+
+ @Test
+ fun testDesktopWindowDisappearsFromDesk() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+ val child = createFreeformTask().apply { parentTaskId = freeformRoot.taskId }
+
+ organizer.onTaskAppeared(child, SurfaceControl())
+ organizer.onTaskVanished(child)
+
+ assertThat(organizer.roots[freeformRoot.taskId].children).doesNotContain(child.taskId)
+ }
+
+ @Test
+ fun testRemoveDesk() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ val wct = WindowContainerTransaction()
+ organizer.removeDesk(wct, freeformRoot.taskId)
+
+ assertThat(
+ wct.hierarchyOps.any { hop ->
+ hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK &&
+ hop.container == freeformRoot.token.asBinder()
+ }
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun testRemoveDesk_didNotExist_throws() {
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+
+ val wct = WindowContainerTransaction()
+ assertThrows(Exception::class.java) { organizer.removeDesk(wct, freeformRoot.taskId) }
+ }
+
+ @Test
+ fun testActivateDesk() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ val wct = WindowContainerTransaction()
+ organizer.activateDesk(wct, freeformRoot.taskId)
+
+ assertThat(
+ wct.hierarchyOps.any { hop ->
+ hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REORDER &&
+ hop.toTop &&
+ hop.container == freeformRoot.token.asBinder()
+ }
+ )
+ .isTrue()
+ assertThat(
+ wct.hierarchyOps.any { hop ->
+ hop.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT &&
+ hop.container == freeformRoot.token.asBinder()
+ }
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun testActivateDesk_didNotExist_throws() {
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+
+ val wct = WindowContainerTransaction()
+ assertThrows(Exception::class.java) { organizer.activateDesk(wct, freeformRoot.taskId) }
+ }
+
+ @Test
+ fun testMoveTaskToDesk() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ val desktopTask = createFreeformTask().apply { parentTaskId = -1 }
+ val wct = WindowContainerTransaction()
+ organizer.moveTaskToDesk(wct, freeformRoot.taskId, desktopTask)
+
+ assertThat(
+ wct.hierarchyOps.any { hop ->
+ hop.isReparent &&
+ hop.toTop &&
+ hop.container == desktopTask.token.asBinder() &&
+ hop.newParent == freeformRoot.token.asBinder()
+ }
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun testMoveTaskToDesk_didNotExist_throws() {
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+
+ val desktopTask = createFreeformTask().apply { parentTaskId = -1 }
+ val wct = WindowContainerTransaction()
+ assertThrows(Exception::class.java) {
+ organizer.moveTaskToDesk(wct, freeformRoot.taskId, desktopTask)
+ }
+ }
+
+ @Test
+ fun testGetDeskAtEnd() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ val task = createFreeformTask().apply { parentTaskId = freeformRoot.taskId }
+ val endDesk =
+ organizer.getDeskAtEnd(
+ TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task }
+ )
+
+ assertThat(endDesk).isEqualTo(freeformRoot.taskId)
+ }
+
+ 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 a3c441698905..9a8f264e98a4 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,6 +17,7 @@
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.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
@@ -24,6 +25,7 @@ import android.view.Display.DEFAULT_DISPLAY
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopUserRepositories
@@ -85,7 +87,9 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
- fun initWithPersistence_multipleUsers_addedCorrectly() =
+ /** TODO: b/362720497 - add multi-desk version when implemented. */
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun initWithPersistence_multipleUsers_addedCorrectly_multiDesksDisabled() =
runTest(StandardTestDispatcher()) {
whenever(persistentRepository.getUserDesktopRepositoryMap())
.thenReturn(
@@ -145,7 +149,9 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
- fun initWithPersistence_singleUser_addedCorrectly() =
+ /** TODO: b/362720497 - add multi-desk version when implemented. */
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun initWithPersistence_singleUser_addedCorrectly_multiDesksDisabled() =
runTest(StandardTestDispatcher()) {
whenever(persistentRepository.getUserDesktopRepositoryMap())
.thenReturn(mapOf(USER_ID_1 to desktopRepositoryState1))
@@ -156,24 +162,24 @@ 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(deskId = DEFAULT_DISPLAY)
)
.containsExactly(1, 3, 4, 5)
.inOrder()
assertThat(
desktopUserRepositories
.getProfile(USER_ID_1)
- .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+ .getExpandedTasksIdsInDeskOrdered(deskId = DEFAULT_DISPLAY)
)
.containsExactly(5, 1)
.inOrder()
assertThat(
- desktopUserRepositories.getProfile(USER_ID_1).getMinimizedTasks(DEFAULT_DISPLAY)
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getMinimizedTaskIdsInDesk(deskId = DEFAULT_DISPLAY)
)
.containsExactly(3, 4)
.inOrder()
@@ -195,6 +201,7 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
val desktop1: Desktop =
Desktop.newBuilder()
.setDesktopId(DESKTOP_ID_1)
+ .setDisplayId(DEFAULT_DISPLAY)
.addAllZOrderedTasks(freeformTasksInZOrder1)
.putTasksByTaskId(
1,
@@ -216,6 +223,7 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
val desktop2: Desktop =
Desktop.newBuilder()
.setDesktopId(DESKTOP_ID_2)
+ .setDisplayId(DEFAULT_DISPLAY)
.addAllZOrderedTasks(freeformTasksInZOrder2)
.putTasksByTaskId(
4,
@@ -237,6 +245,7 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
val desktop3: Desktop =
Desktop.newBuilder()
.setDesktopId(DESKTOP_ID_3)
+ .setDisplayId(DEFAULT_DISPLAY)
.addAllZOrderedTasks(freeformTasksInZOrder3)
.putTasksByTaskId(
7,