From 2ee5002ae5c9004787f2565bd5b6049368bad40c Mon Sep 17 00:00:00 2001 From: Ats Jenk Date: Thu, 13 Apr 2023 18:11:38 -0700 Subject: [PATCH] Store desktop tasks per display id Store active and visible tasks per display id. When notifying the listener of changes, include the display id. Adding support to controllers to bring only tasks to front from current display when switching a task to freeform. Using tasks display id when deciding if a fullscreen task should switch to freeform on launch or if freeform should switch to fullscreen. TODO: - add display id to show desktop calls - update sysui state when tasks are shown/hidden on other displays (currently only default display is supported) - tests for desktop mode proto 1 Bug: 278084491 Test: atest DesktopModeTaskRepositoryTest DesktopTasksControllerTest DesktopModeControllerTest Change-Id: I6ebdc4c9b444c22bf7fe58ce82f8db6544fac5e5 --- .../desktopmode/DesktopModeController.java | 27 +- .../desktopmode/DesktopModeTaskRepository.kt | 151 +++++++---- .../desktopmode/DesktopTasksController.kt | 43 ++-- .../shell/freeform/FreeformTaskListener.java | 12 +- .../shell/recents/RecentTasksController.java | 2 +- .../DesktopModeControllerTest.java | 108 ++++++-- .../DesktopModeTaskRepositoryTest.kt | 236 ++++++++++++++---- .../desktopmode/DesktopTasksControllerTest.kt | 139 +++++++++-- .../shell/desktopmode/DesktopTestHelpers.kt | 13 +- .../com/android/systemui/wmshell/WMShell.java | 17 +- 10 files changed, 561 insertions(+), 187 deletions(-) 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