diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java index 86ea72582a52a..a2b35d37ec7c8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; @@ -264,10 +265,10 @@ public class DesktopModeController implements RemoteCallable activeTasks = mDesktopModeTaskRepository.getActiveTasks(); + private void bringDesktopAppsToFront(int displayId, WindowContainerTransaction wct) { + final ArraySet activeTasks = mDesktopModeTaskRepository.getActiveTasks(displayId); ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size()); final List taskInfos = new ArrayList<>(); @@ -386,6 +387,7 @@ public class DesktopModeController implements RemoteCallable controller.showDesktopApps(DEFAULT_DISPLAY)); } + // TODO(b/278084491): pass in display id @Override public int getVisibleTaskCount() throws RemoteException { int[] result = new int[1]; executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount", - controller -> result[0] = controller.getVisibleTaskCount(), + controller -> result[0] = controller.getVisibleTaskCount(DEFAULT_DISPLAY), true /* blocking */ ); return result[0]; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 12f8ea23ac8fa..00cc57f0b99ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -20,6 +20,8 @@ import android.graphics.Region import android.util.ArrayMap import android.util.ArraySet import android.util.SparseArray +import androidx.core.util.forEach +import androidx.core.util.keyIterator import androidx.core.util.valueIterator import java.util.concurrent.Executor import java.util.function.Consumer @@ -29,14 +31,18 @@ import java.util.function.Consumer */ class DesktopModeTaskRepository { - /** - * Set of task ids that are marked as active in desktop mode. - * Active tasks in desktop mode are freeform tasks that are visible or have been visible after - * desktop mode was activated. - * Task gets removed from this list when it vanishes. Or when desktop mode is turned off. - */ - private val activeTasks = ArraySet() - private val visibleTasks = ArraySet() + /** Task data that is tracked per display */ + private data class DisplayData( + /** + * Set of task ids that are marked as active in desktop mode. Active tasks in desktop mode + * are freeform tasks that are visible or have been visible after desktop mode was + * activated. Task gets removed from this list when it vanishes. Or when desktop mode is + * turned off. + */ + val activeTasks: ArraySet = ArraySet(), + val visibleTasks: ArraySet = ArraySet(), + ) + // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0). private val freeformTasksInZOrder = mutableListOf() private val activeTasksListeners = ArraySet() @@ -47,9 +53,22 @@ class DesktopModeTaskRepository { private var desktopGestureExclusionListener: Consumer? = null private var desktopGestureExclusionExecutor: Executor? = null - /** - * Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository. - */ + private val displayData = + object : SparseArray() { + /** + * Get the [DisplayData] associated with this [displayId] + * + * Creates a new instance if one does not exist + */ + fun getOrCreate(displayId: Int): DisplayData { + if (!contains(displayId)) { + put(displayId, DisplayData()) + } + return get(displayId) + } + } + + /** Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository. */ fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) { activeTasksListeners.add(activeTasksListener) } @@ -57,10 +76,17 @@ class DesktopModeTaskRepository { /** * Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not. */ - fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) { - visibleTasksListeners.put(visibleTasksListener, executor) - executor.execute( - Runnable { visibleTasksListener.onVisibilityChanged(visibleTasks.size > 0) }) + fun addVisibleTasksListener( + visibleTasksListener: VisibleTasksListener, + executor: Executor + ) { + visibleTasksListeners[visibleTasksListener] = executor + displayData.keyIterator().forEach { displayId -> + val visibleTasks = getVisibleTaskCount(displayId) + executor.execute { + visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0) + } + } } /** @@ -100,14 +126,21 @@ class DesktopModeTaskRepository { } /** - * Mark a task with given [taskId] as active. + * Mark a task with given [taskId] as active on given [displayId] * - * @return `true` if the task was not active + * @return `true` if the task was not active on given [displayId] */ - fun addActiveTask(taskId: Int): Boolean { - val added = activeTasks.add(taskId) + fun addActiveTask(displayId: Int, taskId: Int): Boolean { + // Check if task is active on another display, if so, remove it + displayData.forEach { id, data -> + if (id != displayId && data.activeTasks.remove(taskId)) { + activeTasksListeners.onEach { it.onActiveTasksChanged(id) } + } + } + + val added = displayData.getOrCreate(displayId).activeTasks.add(taskId) if (added) { - activeTasksListeners.onEach { it.onActiveTasksChanged() } + activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) } } return added } @@ -118,65 +151,93 @@ class DesktopModeTaskRepository { * @return `true` if the task was active */ fun removeActiveTask(taskId: Int): Boolean { - val removed = activeTasks.remove(taskId) - if (removed) { - activeTasksListeners.onEach { it.onActiveTasksChanged() } + var result = false + displayData.forEach { displayId, data -> + if (data.activeTasks.remove(taskId)) { + activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) } + result = true + } } - return removed + return result } /** * Check if a task with the given [taskId] was marked as an active task */ fun isActiveTask(taskId: Int): Boolean { - return activeTasks.contains(taskId) + return displayData.valueIterator().asSequence().any { data -> + data.activeTasks.contains(taskId) + } } /** * Whether a task is visible. */ fun isVisibleTask(taskId: Int): Boolean { - return visibleTasks.contains(taskId) + return displayData.valueIterator().asSequence().any { data -> + data.visibleTasks.contains(taskId) + } } /** - * Get a set of the active tasks + * Get a set of the active tasks for given [displayId] */ - fun getActiveTasks(): ArraySet { - return ArraySet(activeTasks) + fun getActiveTasks(displayId: Int): ArraySet { + return ArraySet(displayData[displayId]?.activeTasks) } /** * Get a list of freeform tasks, ordered from top-bottom (top at index 0). */ + // TODO(b/278084491): pass in display id fun getFreeformTasksInZOrder(): List { return freeformTasksInZOrder } /** * Updates whether a freeform task with this id is visible or not and notifies listeners. + * + * If the task was visible on a different display with a different displayId, it is removed from + * the set of visible tasks on that display. Listeners will be notified. */ - fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) { - val prevCount: Int = visibleTasks.size + fun updateVisibleFreeformTasks(displayId: Int, taskId: Int, visible: Boolean) { if (visible) { - visibleTasks.add(taskId) - } else { - visibleTasks.remove(taskId) - } - if (prevCount == 0 && visibleTasks.size == 1 || - prevCount > 0 && visibleTasks.size == 0) { - for ((listener, executor) in visibleTasksListeners) { - executor.execute( - Runnable { listener.onVisibilityChanged(visibleTasks.size > 0) }) + // Task is visible. Check if we need to remove it from any other display. + val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId } + for (otherDisplayId in otherDisplays) { + if (displayData[otherDisplayId].visibleTasks.remove(taskId)) { + // Task removed from other display, check if we should notify listeners + if (displayData[otherDisplayId].visibleTasks.isEmpty()) { + notifyVisibleTaskListeners(otherDisplayId, hasVisibleFreeformTasks = false) + } + } } } + + val prevCount = getVisibleTaskCount(displayId) + if (visible) { + displayData.getOrCreate(displayId).visibleTasks.add(taskId) + } else { + displayData[displayId]?.visibleTasks?.remove(taskId) + } + val newCount = getVisibleTaskCount(displayId) + // Check if count changed and if there was no tasks or this is the first task + if (prevCount != newCount && (prevCount == 0 || newCount == 0)) { + notifyVisibleTaskListeners(displayId, newCount > 0) + } + } + + private fun notifyVisibleTaskListeners(displayId: Int, hasVisibleFreeformTasks: Boolean) { + visibleTasksListeners.forEach { (listener, executor) -> + executor.execute { listener.onVisibilityChanged(displayId, hasVisibleFreeformTasks) } + } } /** - * Get number of tasks that are marked as visible + * Get number of tasks that are marked as visible on given [displayId] */ - fun getVisibleTaskCount(): Int { - return visibleTasks.size + fun getVisibleTaskCount(displayId: Int): Int { + return displayData[displayId]?.visibleTasks?.size ?: 0 } /** @@ -226,7 +287,7 @@ class DesktopModeTaskRepository { * Called when the active tasks change in desktop mode. */ @JvmDefault - fun onActiveTasksChanged() {} + fun onActiveTasksChanged(displayId: Int) {} } /** @@ -237,6 +298,6 @@ class DesktopModeTaskRepository { * Called when the desktop starts or stops showing freeform tasks. */ @JvmDefault - fun onVisibilityChanged(hasVisibleFreeformTasks: Boolean) {} + fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {} } } 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 0d5602365578e..5e4844a074108 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 @@ -30,6 +30,7 @@ import android.graphics.Rect import android.graphics.Region import android.os.IBinder import android.os.SystemProperties +import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_NONE @@ -97,10 +98,11 @@ class DesktopTasksController( } /** Show all tasks, that are part of the desktop, on top of launcher */ - fun showDesktopApps() { + fun showDesktopApps(displayId: Int) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps") val wct = WindowContainerTransaction() - bringDesktopAppsToFront(wct) + // TODO(b/278084491): pass in display id + bringDesktopAppsToFront(displayId, wct) // Execute transaction if there are pending operations if (!wct.isEmpty) { @@ -114,8 +116,8 @@ class DesktopTasksController( } /** Get number of tasks that are marked as visible */ - fun getVisibleTaskCount(): Int { - return desktopModeTaskRepository.getVisibleTaskCount() + fun getVisibleTaskCount(displayId: Int): Int { + return desktopModeTaskRepository.getVisibleTaskCount(displayId) } /** Move a task with given `taskId` to desktop */ @@ -129,7 +131,7 @@ class DesktopTasksController( val wct = WindowContainerTransaction() // Bring other apps to front first - bringDesktopAppsToFront(wct) + bringDesktopAppsToFront(task.displayId, wct) addMoveToDesktopChanges(wct, task.token) if (Transitions.ENABLE_SHELL_TRANSITIONS) { transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) @@ -165,7 +167,7 @@ class DesktopTasksController( freeformBounds: Rect ) { val wct = WindowContainerTransaction() - bringDesktopAppsToFront(wct) + bringDesktopAppsToFront(taskInfo.displayId, wct) addMoveToDesktopChanges(wct, taskInfo.getToken()) wct.setBounds(taskInfo.token, freeformBounds) @@ -244,9 +246,9 @@ class DesktopTasksController( ?: WINDOWING_MODE_UNDEFINED } - private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) { + private fun bringDesktopAppsToFront(displayId: Int, wct: WindowContainerTransaction) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront") - val activeTasks = desktopModeTaskRepository.getActiveTasks() + val activeTasks = desktopModeTaskRepository.getActiveTasks(displayId) // First move home to front and then other tasks on top of it moveHomeTaskToFront(wct) @@ -290,18 +292,17 @@ class DesktopTasksController( request: TransitionRequestInfo ): WindowContainerTransaction? { // Check if we should skip handling this transition - val task: RunningTaskInfo? = request.triggerTask val shouldHandleRequest = when { // Only handle open or to front transitions request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false // Only handle when it is a task transition - task == null -> false + request.triggerTask == null -> false // Only handle standard type tasks - task.activityType != ACTIVITY_TYPE_STANDARD -> false + request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> false // Only handle fullscreen or freeform tasks - task.windowingMode != WINDOWING_MODE_FULLSCREEN && - task.windowingMode != WINDOWING_MODE_FREEFORM -> false + request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN && + request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> false // Otherwise process it else -> true } @@ -310,10 +311,11 @@ class DesktopTasksController( return null } - val activeTasks = desktopModeTaskRepository.getActiveTasks() + val task: RunningTaskInfo = request.triggerTask + val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) // Check if we should switch a fullscreen task to freeform - if (task?.windowingMode == WINDOWING_MODE_FULLSCREEN) { + if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) { // If there are any visible desktop tasks, switch the task to freeform if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) { ProtoLog.d( @@ -329,7 +331,7 @@ class DesktopTasksController( } // CHeck if we should switch a freeform task to fullscreen - if (task?.windowingMode == WINDOWING_MODE_FREEFORM) { + if (task.windowingMode == WINDOWING_MODE_FREEFORM) { // If no visible desktop tasks, switch this task to freeform as the transition came // outside of this controller if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) { @@ -559,20 +561,21 @@ class DesktopTasksController( controller = null } + // TODO(b/278084491): pass in display id override fun showDesktopApps() { ExecutorUtils.executeRemoteCallWithTaskPermission( controller, - "showDesktopApps", - Consumer(DesktopTasksController::showDesktopApps) - ) + "showDesktopApps" + ) { c -> c.showDesktopApps(DEFAULT_DISPLAY) } } + // TODO(b/278084491): pass in display id override fun getVisibleTaskCount(): Int { val result = IntArray(1) ExecutorUtils.executeRemoteCallWithTaskPermission( controller, "getVisibleTaskCount", - { controller -> result[0] = controller.getVisibleTaskCount() }, + { controller -> result[0] = controller.getVisibleTaskCount(DEFAULT_DISPLAY) }, true /* blocking */ ) return result[0] diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 48487bc4a3d60..22541bbd892a8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -94,11 +94,12 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, mDesktopModeTaskRepository.ifPresent(repository -> { repository.addOrMoveFreeformTaskToTop(taskInfo.taskId); if (taskInfo.isVisible) { - if (repository.addActiveTask(taskInfo.taskId)) { + if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Adding active freeform task: #%d", taskInfo.taskId); } - repository.updateVisibleFreeformTasks(taskInfo.taskId, true); + repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, + true); } }); } @@ -117,7 +118,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Removing active freeform task: #%d", taskInfo.taskId); } - repository.updateVisibleFreeformTasks(taskInfo.taskId, false); + repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, false); }); } @@ -137,12 +138,13 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, if (DesktopModeStatus.isAnyEnabled()) { mDesktopModeTaskRepository.ifPresent(repository -> { if (taskInfo.isVisible) { - if (repository.addActiveTask(taskInfo.taskId)) { + if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Adding active freeform task: #%d", taskInfo.taskId); } } - repository.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible); + repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, + taskInfo.isVisible); }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index c5bfd8753994b..5c9709c756f7e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -245,7 +245,7 @@ public class RecentTasksController implements TaskStackListenerCallback, } @Override - public void onActiveTasksChanged() { + public void onActiveTasksChanged(int displayId) { notifyRecentTasksChanged(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java index 63de74fa3b054..d6387ee5ae13d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_NONE; @@ -82,6 +83,8 @@ import java.util.Arrays; @RunWith(AndroidTestingRunner.class) public class DesktopModeControllerTest extends ShellTestCase { + private static final int SECOND_DISPLAY = 2; + @Mock private ShellController mShellController; @Mock @@ -248,22 +251,22 @@ public class DesktopModeControllerTest extends ShellTestCase { public void testShowDesktopApps_allAppsInvisible_bringsToFront() { // Set up two active tasks on desktop, task2 is on top of task1. RunningTaskInfo freeformTask1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask1.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId); mDesktopModeTaskRepository.updateVisibleFreeformTasks( - freeformTask1.taskId, false /* visible */); + DEFAULT_DISPLAY, freeformTask1.taskId, false /* visible */); RunningTaskInfo freeformTask2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask2.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId); mDesktopModeTaskRepository.updateVisibleFreeformTasks( - freeformTask2.taskId, false /* visible */); + DEFAULT_DISPLAY, freeformTask2.taskId, false /* visible */); when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn( freeformTask1); when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn( freeformTask2); // Run show desktop apps logic - mController.showDesktopApps(); + mController.showDesktopApps(DEFAULT_DISPLAY); final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); // Check wct has reorder calls @@ -283,17 +286,19 @@ public class DesktopModeControllerTest extends ShellTestCase { @Test public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() { final RunningTaskInfo task1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task1.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId, + true /* visible */); when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1); final RunningTaskInfo task2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task2.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId, + true /* visible */); when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2); - mController.showDesktopApps(); + mController.showDesktopApps(DEFAULT_DISPLAY); final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); // Check wct has reorder calls @@ -312,17 +317,19 @@ public class DesktopModeControllerTest extends ShellTestCase { @Test public void testShowDesktopApps_someAppsInvisible_reordersAll() { final RunningTaskInfo task1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task1.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, false /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId, + false /* visible */); when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1); final RunningTaskInfo task2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task2.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId, + true /* visible */); when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2); - mController.showDesktopApps(); + mController.showDesktopApps(DEFAULT_DISPLAY); final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); // Both tasks should be reordered to top, even if one was already visible. @@ -335,39 +342,88 @@ public class DesktopModeControllerTest extends ShellTestCase { assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder()); } + @Test + public void testShowDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() { + RunningTaskInfo taskDefaultDisplay = createFreeformTask(DEFAULT_DISPLAY); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks( + DEFAULT_DISPLAY, taskDefaultDisplay.taskId, false /* visible */); + when(mShellTaskOrganizer.getRunningTaskInfo(taskDefaultDisplay.taskId)).thenReturn( + taskDefaultDisplay); + + RunningTaskInfo taskSecondDisplay = createFreeformTask(SECOND_DISPLAY); + mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks( + SECOND_DISPLAY, taskSecondDisplay.taskId, false /* visible */); + when(mShellTaskOrganizer.getRunningTaskInfo(taskSecondDisplay.taskId)).thenReturn( + taskSecondDisplay); + + mController.showDesktopApps(DEFAULT_DISPLAY); + + WindowContainerTransaction wct = getBringAppsToFrontTransaction(); + assertThat(wct.getHierarchyOps()).hasSize(1); + HierarchyOp op = wct.getHierarchyOps().get(0); + assertThat(op.getContainer()).isEqualTo(taskDefaultDisplay.token.asBinder()); + } + @Test public void testGetVisibleTaskCount_noTasks_returnsZero() { - assertThat(mController.getVisibleTaskCount()).isEqualTo(0); + assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0); } @Test public void testGetVisibleTaskCount_twoTasks_bothVisible_returnsTwo() { RunningTaskInfo task1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task1.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId, + true /* visible */); RunningTaskInfo task2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task2.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId, + true /* visible */); - assertThat(mController.getVisibleTaskCount()).isEqualTo(2); + assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2); } @Test public void testGetVisibleTaskCount_twoTasks_oneVisible_returnsOne() { RunningTaskInfo task1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task1.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId, + true /* visible */); RunningTaskInfo task2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task2.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, false /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId, + false /* visible */); - assertThat(mController.getVisibleTaskCount()).isEqualTo(1); + assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1); + } + + @Test + public void testGetVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() { + RunningTaskInfo taskDefaultDisplay = createFreeformTask(); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, + taskDefaultDisplay.taskId, + true /* visible */); + + RunningTaskInfo taskSecondDisplay = createFreeformTask(); + mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(SECOND_DISPLAY, + taskSecondDisplay.taskId, + true /* visible */); + + assertThat(mController.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index 45cb3a062cc55..3bc2f0e8674e4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -17,10 +17,12 @@ package com.android.wm.shell.desktopmode import android.testing.AndroidTestingRunner +import android.view.Display.DEFAULT_DISPLAY import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.fail import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -41,8 +43,8 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestListener() repo.addActiveTaskListener(listener) - repo.addActiveTask(1) - assertThat(listener.activeTaskChangedCalls).isEqualTo(1) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1) assertThat(repo.isActiveTask(1)).isTrue() } @@ -51,9 +53,9 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestListener() repo.addActiveTaskListener(listener) - repo.addActiveTask(1) - repo.addActiveTask(1) - assertThat(listener.activeTaskChangedCalls).isEqualTo(1) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1) } @Test @@ -61,9 +63,22 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestListener() repo.addActiveTaskListener(listener) - repo.addActiveTask(1) - repo.addActiveTask(2) - assertThat(listener.activeTaskChangedCalls).isEqualTo(2) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2) + } + + @Test + fun addActiveTask_multipleDisplays_notifiesCorrectListener() { + val listener = TestListener() + repo.addActiveTaskListener(listener) + + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2) + repo.addActiveTask(SECOND_DISPLAY, taskId = 3) + + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2) + assertThat(listener.activeChangesOnSecondaryDisplay).isEqualTo(1) } @Test @@ -71,10 +86,10 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestListener() repo.addActiveTaskListener(listener) - repo.addActiveTask(1) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) repo.removeActiveTask(1) // Notify once for add and once for remove - assertThat(listener.activeTaskChangedCalls).isEqualTo(2) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2) assertThat(repo.isActiveTask(1)).isFalse() } @@ -83,7 +98,17 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestListener() repo.addActiveTaskListener(listener) repo.removeActiveTask(99) - assertThat(listener.activeTaskChangedCalls).isEqualTo(0) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(0) + } + + @Test + fun remoteActiveTask_listenerForOtherDisplayNotNotified() { + val listener = TestListener() + repo.addActiveTaskListener(listener) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + repo.removeActiveTask(1) + assertThat(listener.activeChangesOnSecondaryDisplay).isEqualTo(0) + assertThat(repo.isActiveTask(1)).isFalse() } @Test @@ -93,14 +118,27 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { @Test fun addListener_notifiesVisibleFreeformTask() { - repo.updateVisibleFreeformTasks(1, true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) val listener = TestVisibilityListener() val executor = TestShellExecutor() repo.addVisibleTasksListener(listener, executor) executor.flushAll() - assertThat(listener.hasVisibleFreeformTasks).isTrue() - assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(1) + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue() + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) + } + + @Test + fun addListener_tasksOnDifferentDisplay_doesNotNotify() { + repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true) + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + executor.flushAll() + + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse() + // One call as adding listener notifies it + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(0) } @Test @@ -108,13 +146,61 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestVisibilityListener() val executor = TestShellExecutor() repo.addVisibleTasksListener(listener, executor) - repo.updateVisibleFreeformTasks(1, true) - repo.updateVisibleFreeformTasks(2, true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) executor.flushAll() - assertThat(listener.hasVisibleFreeformTasks).isTrue() - // Equal to 2 because adding the listener notifies the current state - assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2) + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue() + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) + } + + @Test + fun updateVisibleFreeformTasks_addVisibleTaskNotifiesListenerForThatDisplay() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + executor.flushAll() + + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue() + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) + assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isFalse() + assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(0) + + repo.updateVisibleFreeformTasks(displayId = 1, taskId = 2, visible = true) + executor.flushAll() + + // Listener for secondary display is notified + assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue() + assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1) + // No changes to listener for default display + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) + } + + @Test + fun updateVisibleFreeformTasks_taskOnDefaultBecomesVisibleOnSecondDisplay_listenersNotified() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + executor.flushAll() + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue() + + // Mark task 1 visible on secondary display + repo.updateVisibleFreeformTasks(displayId = 1, taskId = 1, visible = true) + executor.flushAll() + + // Default display should have 2 calls + // 1 - visible task added + // 2 - visible task removed + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2) + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse() + + // Secondary display should have 1 call for visible task added + assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1) + assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue() } @Test @@ -122,52 +208,83 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestVisibilityListener() val executor = TestShellExecutor() repo.addVisibleTasksListener(listener, executor) - repo.updateVisibleFreeformTasks(1, true) - repo.updateVisibleFreeformTasks(2, true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) executor.flushAll() - assertThat(listener.hasVisibleFreeformTasks).isTrue() - repo.updateVisibleFreeformTasks(1, false) + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue() + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false) executor.flushAll() - // Equal to 2 because adding the listener notifies the current state - assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2) + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) - repo.updateVisibleFreeformTasks(2, false) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false) executor.flushAll() - assertThat(listener.hasVisibleFreeformTasks).isFalse() - assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3) + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse() + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2) } @Test fun getVisibleTaskCount() { // No tasks, count is 0 - assertThat(repo.getVisibleTaskCount()).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) // New task increments count to 1 - repo.updateVisibleFreeformTasks(taskId = 1, visible = true) - assertThat(repo.getVisibleTaskCount()).isEqualTo(1) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) // Visibility update to same task does not increase count - repo.updateVisibleFreeformTasks(taskId = 1, visible = true) - assertThat(repo.getVisibleTaskCount()).isEqualTo(1) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) // Second task visible increments count - repo.updateVisibleFreeformTasks(taskId = 2, visible = true) - assertThat(repo.getVisibleTaskCount()).isEqualTo(2) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2) // Hiding a task decrements count - repo.updateVisibleFreeformTasks(taskId = 1, visible = false) - assertThat(repo.getVisibleTaskCount()).isEqualTo(1) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) // Hiding all tasks leaves count at 0 - repo.updateVisibleFreeformTasks(taskId = 2, visible = false) - assertThat(repo.getVisibleTaskCount()).isEqualTo(0) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false) + assertThat(repo.getVisibleTaskCount(displayId = 9)).isEqualTo(0) // Hiding a not existing task, count remains at 0 - repo.updateVisibleFreeformTasks(taskId = 999, visible = false) - assertThat(repo.getVisibleTaskCount()).isEqualTo(0) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 999, visible = false) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + } + + @Test + fun getVisibleTaskCount_multipleDisplays() { + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0) + + // New task on default display increments count for that display only + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0) + + // New task on secondary display, increments count for that display only + repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 2, visible = true) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) + + // Marking task visible on another display, updates counts for both displays + repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2) + + // Marking task that is on secondary display, hidden on default display, does not affect + // secondary display + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2) + + // Hiding a task on that display, decrements count + repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = false) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) } @Test @@ -197,19 +314,40 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } class TestListener : DesktopModeTaskRepository.ActiveTasksListener { - var activeTaskChangedCalls = 0 - override fun onActiveTasksChanged() { - activeTaskChangedCalls++ + var activeChangesOnDefaultDisplay = 0 + var activeChangesOnSecondaryDisplay = 0 + override fun onActiveTasksChanged(displayId: Int) { + when (displayId) { + DEFAULT_DISPLAY -> activeChangesOnDefaultDisplay++ + SECOND_DISPLAY -> activeChangesOnSecondaryDisplay++ + else -> fail("Active task listener received unexpected display id: $displayId") + } } } class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener { - var hasVisibleFreeformTasks = false - var visibleFreeformTaskChangedCalls = 0 + var hasVisibleTasksOnDefaultDisplay = false + var hasVisibleTasksOnSecondaryDisplay = false - override fun onVisibilityChanged(hasVisibleTasks: Boolean) { - hasVisibleFreeformTasks = hasVisibleTasks - visibleFreeformTaskChangedCalls++ + var visibleChangesOnDefaultDisplay = 0 + var visibleChangesOnSecondaryDisplay = 0 + + override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) { + when (displayId) { + DEFAULT_DISPLAY -> { + hasVisibleTasksOnDefaultDisplay = hasVisibleFreeformTasks + visibleChangesOnDefaultDisplay++ + } + SECOND_DISPLAY -> { + hasVisibleTasksOnSecondaryDisplay = hasVisibleFreeformTasks + visibleChangesOnSecondaryDisplay++ + } + else -> fail("Visible task listener received unexpected display id: $displayId") + } } } + + companion object { + const val SECOND_DISPLAY = 1 + } } 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 c9bd695ffb33c..f506969f51df2 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 @@ -25,6 +25,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.os.Binder import android.testing.AndroidTestingRunner +import android.view.Display.DEFAULT_DISPLAY import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_NONE @@ -84,10 +85,10 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler - lateinit var mockitoSession: StaticMockitoSession - lateinit var controller: DesktopTasksController - lateinit var shellInit: ShellInit - lateinit var desktopModeTaskRepository: DesktopModeTaskRepository + private lateinit var mockitoSession: StaticMockitoSession + private lateinit var controller: DesktopTasksController + private lateinit var shellInit: ShellInit + private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository // Mock running tasks are registered here so we can get the list from mock shell task organizer private val runningTasks = mutableListOf() @@ -155,7 +156,7 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskHidden(task1) markTaskHidden(task2) - controller.showDesktopApps() + controller.showDesktopApps(DEFAULT_DISPLAY) val wct = getLatestWct(expectTransition = TRANSIT_NONE) assertThat(wct.hierarchyOps).hasSize(3) @@ -173,7 +174,7 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskVisible(task1) markTaskVisible(task2) - controller.showDesktopApps() + controller.showDesktopApps(DEFAULT_DISPLAY) val wct = getLatestWct(expectTransition = TRANSIT_NONE) assertThat(wct.hierarchyOps).hasSize(3) @@ -191,7 +192,7 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskHidden(task1) markTaskVisible(task2) - controller.showDesktopApps() + controller.showDesktopApps(DEFAULT_DISPLAY) val wct = getLatestWct(expectTransition = TRANSIT_NONE) assertThat(wct.hierarchyOps).hasSize(3) @@ -205,16 +206,34 @@ class DesktopTasksControllerTest : ShellTestCase() { fun showDesktopApps_noActiveTasks_reorderHomeToTop() { val homeTask = setUpHomeTask() - controller.showDesktopApps() + controller.showDesktopApps(DEFAULT_DISPLAY) val wct = getLatestWct(expectTransition = TRANSIT_NONE) assertThat(wct.hierarchyOps).hasSize(1) wct.assertReorderAt(index = 0, homeTask) } + @Test + fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() { + val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY) + val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY) + setUpHomeTask(SECOND_DISPLAY) + val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY) + markTaskHidden(taskDefaultDisplay) + markTaskHidden(taskSecondDisplay) + + controller.showDesktopApps(DEFAULT_DISPLAY) + + val wct = getLatestWct(expectTransition = TRANSIT_NONE) + assertThat(wct.hierarchyOps).hasSize(2) + // Expect order to be from bottom: home, task + wct.assertReorderAt(index = 0, homeTaskDefaultDisplay) + wct.assertReorderAt(index = 1, taskDefaultDisplay) + } + @Test fun getVisibleTaskCount_noTasks_returnsZero() { - assertThat(controller.getVisibleTaskCount()).isEqualTo(0) + assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) } @Test @@ -222,7 +241,7 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpHomeTask() setUpFreeformTask().also(::markTaskVisible) setUpFreeformTask().also(::markTaskVisible) - assertThat(controller.getVisibleTaskCount()).isEqualTo(2) + assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2) } @Test @@ -230,7 +249,15 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpHomeTask() setUpFreeformTask().also(::markTaskVisible) setUpFreeformTask().also(::markTaskHidden) - assertThat(controller.getVisibleTaskCount()).isEqualTo(1) + assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) + } + + @Test + fun getVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() { + setUpHomeTask() + setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible) + setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible) + assertThat(controller.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) } @Test @@ -258,6 +285,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToDesktop(fullscreenTask) with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + // Operations should include home task, freeform task assertThat(hierarchyOps).hasSize(3) assertReorderSequence(homeTask, freeformTask, fullscreenTask) assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode) @@ -265,6 +293,28 @@ class DesktopTasksControllerTest : ShellTestCase() { } } + @Test + fun moveToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() { + setUpHomeTask(displayId = DEFAULT_DISPLAY) + val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + markTaskHidden(freeformTaskDefault) + + val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY) + val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY) + markTaskHidden(freeformTaskSecond) + + controller.moveToDesktop(fullscreenTaskDefault) + + with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + // Check that hierarchy operations do not include tasks from second display + assertThat(hierarchyOps.map { it.container }) + .doesNotContain(homeTaskSecond.token.asBinder()) + assertThat(hierarchyOps.map { it.container }) + .doesNotContain(freeformTaskSecond.token.asBinder()) + } + } + @Test fun moveToFullscreen() { val task = setUpFreeformTask() @@ -280,6 +330,19 @@ class DesktopTasksControllerTest : ShellTestCase() { verifyWCTNotExecuted() } + @Test + fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() { + val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY) + + controller.moveToFullscreen(taskDefaultDisplay) + + with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder()) + assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder()) + } + } + @Test fun getTaskWindowingMode() { val fullscreenTask = setUpFullscreenTask() @@ -323,6 +386,18 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() } + @Test + fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY) + createFreeformTask(displayId = SECOND_DISPLAY) + + val result = + controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay)) + assertThat(result).isNull() + } + @Test fun handleRequest_freeformTask_freeformVisible_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -361,6 +436,18 @@ class DesktopTasksControllerTest : ShellTestCase() { .isEqualTo(WINDOWING_MODE_FULLSCREEN) } + @Test + fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_returnSwitchToFullscreenWCT() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) + createFreeformTask(displayId = SECOND_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay)) + assertThat(result?.changes?.get(taskDefaultDisplay.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + } + @Test fun handleRequest_notOpenOrToFrontTransition_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -400,35 +487,43 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() } - private fun setUpFreeformTask(): RunningTaskInfo { - val task = createFreeformTask() + private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + val task = createFreeformTask(displayId) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) - desktopModeTaskRepository.addActiveTask(task.taskId) + desktopModeTaskRepository.addActiveTask(displayId, task.taskId) desktopModeTaskRepository.addOrMoveFreeformTaskToTop(task.taskId) runningTasks.add(task) return task } - private fun setUpHomeTask(): RunningTaskInfo { - val task = createHomeTask() + private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + val task = createHomeTask(displayId) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) return task } - private fun setUpFullscreenTask(): RunningTaskInfo { - val task = createFullscreenTask() + private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + val task = createFullscreenTask(displayId) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) return task } private fun markTaskVisible(task: RunningTaskInfo) { - desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = true) + desktopModeTaskRepository.updateVisibleFreeformTasks( + task.displayId, + task.taskId, + visible = true + ) } private fun markTaskHidden(task: RunningTaskInfo) { - desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = false) + desktopModeTaskRepository.updateVisibleFreeformTasks( + task.displayId, + task.taskId, + visible = false + ) } private fun getLatestWct( @@ -457,6 +552,10 @@ class DesktopTasksControllerTest : ShellTestCase() { ): TransitionRequestInfo { return TransitionRequestInfo(type, task, null /* remoteTransition */) } + + companion object { + const val SECOND_DISPLAY = 2 + } } private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) { 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 dc91d756842ec..cf1ff3214d872 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 @@ -21,14 +21,17 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.view.Display.DEFAULT_DISPLAY import com.android.wm.shell.TestRunningTaskInfoBuilder class DesktopTestHelpers { companion object { /** Create a task that has windowing mode set to [WINDOWING_MODE_FREEFORM] */ @JvmStatic - fun createFreeformTask(): RunningTaskInfo { + @JvmOverloads + fun createFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { return TestRunningTaskInfoBuilder() + .setDisplayId(displayId) .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(WINDOWING_MODE_FREEFORM) @@ -38,8 +41,10 @@ class DesktopTestHelpers { /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */ @JvmStatic - fun createFullscreenTask(): RunningTaskInfo { + @JvmOverloads + fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { return TestRunningTaskInfoBuilder() + .setDisplayId(displayId) .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(WINDOWING_MODE_FULLSCREEN) @@ -49,8 +54,10 @@ class DesktopTestHelpers { /** Create a new home task */ @JvmStatic - fun createHomeTask(): RunningTaskInfo { + @JvmOverloads + fun createHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { return TestRunningTaskInfoBuilder() + .setDisplayId(displayId) .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_HOME) .setWindowingMode(WINDOWING_MODE_FULLSCREEN) diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index b3e7cb0c77eb7..e60f9b65dc1c4 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -33,6 +33,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; import android.os.IBinder; +import android.view.Display; import android.view.KeyEvent; import androidx.annotation.NonNull; @@ -347,12 +348,16 @@ public final class WMShell implements void initDesktopMode(DesktopMode desktopMode) { desktopMode.addVisibleTasksListener( new DesktopModeTaskRepository.VisibleTasksListener() { - @Override - public void onVisibilityChanged(boolean hasFreeformTasks) { - mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, hasFreeformTasks) - .commitUpdate(mDisplayTracker.getDefaultDisplayId()); - } - }, mSysUiMainExecutor); + @Override + public void onVisibilityChanged(int displayId, boolean hasFreeformTasks) { + if (displayId == Display.DEFAULT_DISPLAY) { + mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, + hasFreeformTasks) + .commitUpdate(mDisplayTracker.getDefaultDisplayId()); + } + // TODO(b/278084491): update sysui state for changes on other displays + } + }, mSysUiMainExecutor); } @Override