summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java13
-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.kt31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt58
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt48
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt39
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt124
11 files changed, 408 insertions, 29 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 c81838f56a74..0f232d50f2fc 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
@@ -111,6 +111,7 @@ 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.DesksTransitionObserver;
import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer;
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer;
@@ -760,6 +761,7 @@ public abstract class WMShellModule {
Optional<BubbleController> bubbleController,
OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
DesksOrganizer desksOrganizer,
+ DesksTransitionObserver desksTransitionObserver,
UserProfileContexts userProfileContexts,
DesktopModeCompatPolicy desktopModeCompatPolicy) {
return new DesktopTasksController(
@@ -797,6 +799,7 @@ public abstract class WMShellModule {
bubbleController,
overviewToDesktopTransitionObserver,
desksOrganizer,
+ desksTransitionObserver,
userProfileContexts,
desktopModeCompatPolicy);
}
@@ -1134,6 +1137,7 @@ public abstract class WMShellModule {
Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
Optional<BackAnimationController> backAnimationController,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
+ @NonNull DesksTransitionObserver desksTransitionObserver,
ShellInit shellInit) {
return desktopUserRepositories.flatMap(
repository ->
@@ -1146,11 +1150,20 @@ public abstract class WMShellModule {
desktopMixedTransitionHandler.get(),
backAnimationController.get(),
desktopWallpaperActivityTokenProvider,
+ desksTransitionObserver,
shellInit)));
}
@WMSingleton
@Provides
+ static DesksTransitionObserver provideDesksTransitionObserver(
+ @NonNull @DynamicOverride DesktopUserRepositories desktopUserRepositories
+ ) {
+ return new DesksTransitionObserver(desktopUserRepositories);
+ }
+
+ @WMSingleton
+ @Provides
static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
Context context,
Transitions transitions,
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 164d04bbde65..b93d2e396402 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
@@ -152,8 +152,8 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
pw.println("Error: desk id should be an integer")
return false
}
- pw.println("Not implemented.")
- return false
+ controller.removeDesk(deskId)
+ return true
}
private fun runRemoveAllDesks(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 4ff1a5f1be31..043b353ba380 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
@@ -174,6 +174,9 @@ class DesktopRepository(
/** Returns the number of desks in the given display. */
fun getNumberOfDesks(displayId: Int) = desktopData.getNumberOfDesks(displayId)
+ /** Returns the display the given desk is in. */
+ fun getDisplayForDesk(deskId: Int) = desktopData.getDisplayForDesk(deskId)
+
/** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */
fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
desktopGestureExclusionListener = regionListener
@@ -207,6 +210,14 @@ class DesktopRepository(
desktopData.createDesk(displayId, deskId)
}
+ /** Returns the ids of the existing desks in the given display. */
+ @VisibleForTesting
+ fun getDeskIds(displayId: Int): Set<Int> =
+ desktopData.desksSequence(displayId).map { desk -> desk.deskId }.toSet()
+
+ /** Returns the id of the default desk in the given display. */
+ fun getDefaultDeskId(displayId: Int): Int? = getDefaultDesk(displayId)?.deskId
+
/** Returns the default desk in the given display. */
private fun getDefaultDesk(displayId: Int): Desk? = desktopData.getDefaultDesk(displayId)
@@ -716,17 +727,13 @@ class DesktopRepository(
}
}
- /**
- * Removes the active desk for the given [displayId] and returns the active tasks on that desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun removeDesk(displayId: Int): ArraySet<Int> {
- val desk = desktopData.getActiveDesk(displayId)
- if (desk == null) {
- logW("Could not find desk to remove: displayId=%d", displayId)
- return ArraySet()
- }
+ /** Removes the given desk and returns the active tasks in that desk. */
+ fun removeDesk(deskId: Int): Set<Int> {
+ val desk =
+ desktopData.getDesk(deskId)
+ ?: return emptySet<Int>().also {
+ logW("Could not find desk to remove: deskId=%d", deskId)
+ }
val activeTasks = ArraySet(desk.activeTasks)
desktopData.remove(desk.deskId)
return activeTasks
@@ -1066,7 +1073,7 @@ class DesktopRepository(
}
override fun getDisplayForDesk(deskId: Int): Int =
- getAllActiveDesks().find { it.deskId == deskId }?.displayId
+ desksSequence().find { it.deskId == deskId }?.displayId
?: error("Display for desk=$deskId not found")
}
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 06a64f32f0bc..3f88e7bddd34 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
@@ -103,7 +103,9 @@ 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.DeskTransition
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
@@ -185,6 +187,7 @@ class DesktopTasksController(
private val bubbleController: Optional<BubbleController>,
private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
private val desksOrganizer: DesksOrganizer,
+ private val desksTransitionObserver: DesksTransitionObserver,
private val userProfileContexts: UserProfileContexts,
private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
) :
@@ -2374,20 +2377,62 @@ class DesktopTasksController(
)
}
- fun removeDesktop(displayId: Int) {
+ /** Removes the default desk in the given display. */
+ @Deprecated("Deprecated with multi-desks.", ReplaceWith("removeDesk()"))
+ fun removeDefaultDeskInDisplay(displayId: Int) {
+ val deskId =
+ checkNotNull(taskRepository.getDefaultDeskId(displayId)) {
+ "Expected a default desk to exist"
+ }
+ removeDesk(displayId = displayId, deskId = deskId)
+ }
+
+ /** Removes the given desk. */
+ fun removeDesk(deskId: Int) {
+ val displayId = taskRepository.getDisplayForDesk(deskId)
+ removeDesk(displayId = displayId, deskId = deskId)
+ }
+
+ private fun removeDesk(displayId: Int, deskId: Int) {
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
+ logV("removeDesk deskId=%d from displayId=%d", deskId, displayId)
- val tasksToRemove = taskRepository.removeDesk(displayId)
- val wct = WindowContainerTransaction()
- tasksToRemove.forEach {
- val task = shellTaskOrganizer.getRunningTaskInfo(it)
- if (task != null) {
- wct.removeTask(task.token)
+ val tasksToRemove =
+ if (Flags.enableMultipleDesktopsBackend()) {
+ taskRepository.getActiveTaskIdsInDesk(deskId)
} else {
- recentTasksController?.removeBackgroundTask(it)
+ // TODO: 362720497 - make sure minimized windows are also removed in WM
+ // and the repository.
+ taskRepository.removeDesk(deskId)
+ }
+
+ val wct = WindowContainerTransaction()
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ tasksToRemove.forEach {
+ val task = shellTaskOrganizer.getRunningTaskInfo(it)
+ if (task != null) {
+ wct.removeTask(task.token)
+ } else {
+ recentTasksController?.removeBackgroundTask(it)
+ }
}
+ } else {
+ // TODO: 362720497 - double check background tasks are also removed.
+ desksOrganizer.removeDesk(wct, deskId)
+ }
+ if (!Flags.enableMultipleDesktopsBackend() && wct.isEmpty) return
+ val transition = transitions.startTransition(TRANSIT_CLOSE, wct, /* handler= */ null)
+ if (Flags.enableMultipleDesktopsBackend()) {
+ desksTransitionObserver.addPendingTransition(
+ DeskTransition.RemoveDesk(
+ token = transition,
+ displayId = displayId,
+ deskId = deskId,
+ tasks = tasksToRemove,
+ onDeskRemovedListener = onDeskRemovedListener,
+ )
+ )
}
- if (!wct.isEmpty) transitions.startTransition(TRANSIT_CLOSE, wct, null)
}
/** Enter split by using the focused desktop task in given `displayId`. */
@@ -3091,7 +3136,7 @@ class DesktopTasksController(
override fun removeDesktop(displayId: Int) {
executeRemoteCallWithTaskPermission(controller, "removeDesktop") { c ->
- c.removeDesktop(displayId)
+ c.removeDefaultDeskInDisplay(displayId)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index b3648699ed0b..3ada988ba2a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -37,6 +37,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -58,6 +59,7 @@ class DesktopTasksTransitionObserver(
private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
private val backAnimationController: BackAnimationController,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
+ private val desksTransitionObserver: DesksTransitionObserver,
shellInit: ShellInit,
) : Transitions.TransitionObserver {
@@ -87,6 +89,7 @@ class DesktopTasksTransitionObserver(
finishTransaction: SurfaceControl.Transaction,
) {
// TODO: b/332682201 Update repository state
+ desksTransitionObserver.onTransitionReady(transition, info)
if (
DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
.isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
new file mode 100644
index 000000000000..47088c0b545a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.os.IBinder
+
+/** Represents shell-started transitions involving desks. */
+sealed class DeskTransition {
+ /** The transition token. */
+ abstract val token: IBinder
+
+ /** A transition to remove a desk and its tasks from a display. */
+ data class RemoveDesk(
+ override val token: IBinder,
+ val displayId: Int,
+ val deskId: Int,
+ val tasks: Set<Int>,
+ val onDeskRemovedListener: OnDeskRemovedListener?,
+ ) : DeskTransition()
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
new file mode 100644
index 000000000000..3e49b8a4538b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.os.IBinder
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import com.android.window.flags.Flags
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+
+/**
+ * Observer of desk-related transitions, such as adding, removing or activating a whole desk. It
+ * tracks pending transitions and updates repository state once they finish.
+ */
+class DesksTransitionObserver(private val desktopUserRepositories: DesktopUserRepositories) {
+ private val deskTransitions = mutableMapOf<IBinder, DeskTransition>()
+
+ /** Adds a pending desk transition to be tracked. */
+ fun addPendingTransition(transition: DeskTransition) {
+ if (!Flags.enableMultipleDesktopsBackend()) return
+ deskTransitions[transition.token] = transition
+ }
+
+ /**
+ * Called when any transition is ready, which may include transitions not tracked by this
+ * observer.
+ */
+ fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
+ if (!Flags.enableMultipleDesktopsBackend()) return
+ val deskTransition = deskTransitions.remove(transition) ?: return
+ val desktopRepository = desktopUserRepositories.current
+ when (deskTransition) {
+ is DeskTransition.RemoveDesk -> {
+ check(info.type == TRANSIT_CLOSE) { "Expected close transition for desk removal" }
+ // TODO: b/362720497 - consider verifying the desk was actually removed through the
+ // DesksOrganizer. The transition info won't have changes if the desk was not
+ // visible, such as when dismissing from Overview.
+ val deskId = deskTransition.deskId
+ val displayId = deskTransition.displayId
+ desktopRepository.removeDesk(deskTransition.deskId)
+ deskTransition.onDeskRemovedListener?.onDeskRemoved(displayId, deskId)
+ }
+ }
+ }
+}
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 90f342f91a38..6a343c56d364 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
@@ -27,6 +27,7 @@ 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.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.ShellExecutor
@@ -35,6 +36,7 @@ import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.fail
+import kotlin.test.assertEquals
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -1076,13 +1078,37 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
- val tasksBeforeRemoval = repo.removeDesk(displayId = DEFAULT_DISPLAY)
+ val tasksBeforeRemoval = repo.removeDesk(deskId = DEFAULT_DISPLAY)
assertThat(tasksBeforeRemoval).containsExactly(1, 2, 3).inOrder()
assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty()
}
@Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleDesks_active_removes() {
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+ repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+
+ repo.removeDesk(deskId = 3)
+
+ assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(3)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleDesks_inactive_removes() {
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+ repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+
+ repo.removeDesk(deskId = 2)
+
+ assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(2)
+ }
+
+ @Test
fun getTaskInFullImmersiveState_byDisplay() {
repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -1164,6 +1190,26 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
assertThat(repo.getActiveTaskIdsInDesk(999)).contains(6)
}
+ @Test
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun getDisplayForDesk() {
+ repo.addDesk(SECOND_DISPLAY, SECOND_DISPLAY)
+
+ assertEquals(SECOND_DISPLAY, repo.getDisplayForDesk(deskId = SECOND_DISPLAY))
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun getDisplayForDesk_multipleDesks() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 7)
+ repo.addDesk(SECOND_DISPLAY, deskId = 8)
+ repo.addDesk(SECOND_DISPLAY, deskId = 9)
+
+ assertEquals(DEFAULT_DISPLAY, repo.getDisplayForDesk(deskId = 7))
+ assertEquals(SECOND_DISPLAY, repo.getDisplayForDesk(deskId = 8))
+ }
+
class TestListener : DesktopRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 05ecd5d4b72f..8e7545c2a99c 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
@@ -118,7 +118,9 @@ 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.DeskTransition
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
@@ -180,6 +182,7 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
@@ -251,6 +254,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
private lateinit var overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver
@Mock private lateinit var desksOrganizer: DesksOrganizer
@Mock private lateinit var userProfileContexts: UserProfileContexts
+ @Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
@@ -406,6 +410,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
Optional.of(bubbleController),
overviewToDesktopTransitionObserver,
desksOrganizer,
+ desksTransitionsObserver,
userProfileContexts,
desktopModeCompatPolicy,
)
@@ -3619,13 +3624,14 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun removeDesktop_multipleTasks_removesAll() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleTasks_removesAll() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
val task3 = setUpFreeformTask()
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
- controller.removeDesktop(displayId = DEFAULT_DISPLAY)
+ controller.removeDefaultDeskInDisplay(displayId = DEFAULT_DISPLAY)
val wct = getLatestWct(TRANSIT_CLOSE)
assertThat(wct.hierarchyOps).hasSize(3)
@@ -3636,14 +3642,15 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun removeDesktop_multipleTasksWithBackgroundTask_removesAll() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleTasksWithBackgroundTask_removesAll() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
val task3 = setUpFreeformTask()
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
whenever(shellTaskOrganizer.getRunningTaskInfo(task3.taskId)).thenReturn(null)
- controller.removeDesktop(displayId = DEFAULT_DISPLAY)
+ controller.removeDefaultDeskInDisplay(displayId = DEFAULT_DISPLAY)
val wct = getLatestWct(TRANSIT_CLOSE)
assertThat(wct.hierarchyOps).hasSize(2)
@@ -3653,6 +3660,30 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun removeDesk_multipleDesks_addsPendingTransition() {
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_CLOSE), any(), anyOrNull()))
+ .thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 2)
+
+ controller.removeDesk(deskId = 2)
+
+ verify(desksOrganizer).removeDesk(any(), eq(2))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.RemoveDesk &&
+ this.token == transition &&
+ this.deskId == 2
+ }
+ )
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
val spyController = spy(controller)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 091159c67db5..c29edece5537 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -47,11 +47,13 @@ import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
+import com.android.wm.shell.util.StubTransaction
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
@@ -92,6 +94,7 @@ class DesktopTasksTransitionObserverTest {
private val backAnimationController = mock<BackAnimationController>()
private val desktopWallpaperActivityTokenProvider =
mock<DesktopWallpaperActivityTokenProvider>()
+ private val desksTransitionObserver = mock<DesksTransitionObserver>()
private val wallpaperToken = MockToken().token()
private lateinit var transitionObserver: DesktopTasksTransitionObserver
@@ -115,6 +118,7 @@ class DesktopTasksTransitionObserverTest {
mixedHandler,
backAnimationController,
desktopWallpaperActivityTokenProvider,
+ desksTransitionObserver,
shellInit,
)
}
@@ -411,6 +415,21 @@ class DesktopTasksTransitionObserverTest {
verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
}
+ @Test
+ fun onTransitionReady_forwardsToDesksTransitionObserver() {
+ val transition = Binder()
+ val info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0)
+
+ transitionObserver.onTransitionReady(
+ transition = transition,
+ info = info,
+ StubTransaction(),
+ StubTransaction(),
+ )
+
+ verify(desksTransitionObserver).onTransitionReady(transition, info)
+ }
+
private fun createBackNavigationTransition(
task: RunningTaskInfo?,
type: Int = TRANSIT_TO_BACK,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
new file mode 100644
index 000000000000..bfbaa84e9312
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 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.os.Binder
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import com.android.wm.shell.sysui.ShellInit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Tests for [DesksTransitionObserver].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesksTransitionObserverTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesksTransitionObserverTest : ShellTestCase() {
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private lateinit var desktopUserRepositories: DesktopUserRepositories
+ private lateinit var observer: DesksTransitionObserver
+
+ private val repository: DesktopRepository
+ get() = desktopUserRepositories.current
+
+ @Before
+ fun setUp() {
+ desktopUserRepositories =
+ DesktopUserRepositories(
+ context,
+ ShellInit(TestShellExecutor()),
+ /* shellController= */ mock(),
+ /* persistentRepository= */ mock(),
+ /* repositoryInitializer= */ mock(),
+ /* mainCoroutineScope= */ mock(),
+ /* userManager= */ mock(),
+ )
+ observer = DesksTransitionObserver(desktopUserRepositories)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_removeDesk_removesFromRepository() {
+ val transition = Binder()
+ val removeTransition =
+ DeskTransition.RemoveDesk(
+ transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = 5,
+ tasks = setOf(10, 11),
+ onDeskRemovedListener = null,
+ )
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(removeTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0),
+ )
+
+ assertThat(repository.getDeskIds(DEFAULT_DISPLAY)).doesNotContain(5)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_removeDesk_invokesOnRemoveListener() {
+ class FakeOnDeskRemovedListener : OnDeskRemovedListener {
+ var lastDeskRemoved: Int? = null
+
+ override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
+ lastDeskRemoved = deskId
+ }
+ }
+ val transition = Binder()
+ val removeListener = FakeOnDeskRemovedListener()
+ val removeTransition =
+ DeskTransition.RemoveDesk(
+ transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = 5,
+ tasks = setOf(10, 11),
+ onDeskRemovedListener = removeListener,
+ )
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(removeTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0),
+ )
+
+ assertThat(removeListener.lastDeskRemoved).isEqualTo(5)
+ }
+}