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
This commit is contained in:
Ats Jenk
2023-04-13 18:11:38 -07:00
parent f2c4080992
commit 2ee5002ae5
10 changed files with 561 additions and 187 deletions

View File

@@ -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<DesktopModeControll
/**
* Show apps on desktop
*/
void showDesktopApps() {
void showDesktopApps(int displayId) {
// Bring apps to front, ignoring their visibility status to always ensure they are on top.
WindowContainerTransaction wct = new WindowContainerTransaction();
bringDesktopAppsToFront(wct);
bringDesktopAppsToFront(displayId, wct);
if (!wct.isEmpty()) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -280,12 +281,12 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
}
/** Get number of tasks that are marked as visible */
int getVisibleTaskCount() {
return mDesktopModeTaskRepository.getVisibleTaskCount();
int getVisibleTaskCount(int displayId) {
return mDesktopModeTaskRepository.getVisibleTaskCount(displayId);
}
private void bringDesktopAppsToFront(WindowContainerTransaction wct) {
final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
private void bringDesktopAppsToFront(int displayId, WindowContainerTransaction wct) {
final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(displayId);
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
final List<RunningTaskInfo> taskInfos = new ArrayList<>();
@@ -386,6 +387,7 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
RunningTaskInfo triggerTask = request.getTriggerTask();
// Only do anything if we are in desktop mode and opening/moving-to-front a task/app in
// freeform
if (!DesktopModeStatus.isActive(mContext)) {
@@ -399,16 +401,15 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
WindowManager.transitTypeToString(request.getType()));
return null;
}
if (request.getTriggerTask() == null
|| request.getTriggerTask().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
if (triggerTask == null || triggerTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "skip shell transition request: not freeform task");
return null;
}
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request);
WindowContainerTransaction wct = new WindowContainerTransaction();
bringDesktopAppsToFront(wct);
wct.reorder(request.getTriggerTask().token, true /* onTop */);
bringDesktopAppsToFront(triggerTask.displayId, wct);
wct.reorder(triggerTask.token, true /* onTop */);
return wct;
}
@@ -493,16 +494,18 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
mController = null;
}
// TODO(b/278084491): pass in display id
public void showDesktopApps() {
executeRemoteCallWithTaskPermission(mController, "showDesktopApps",
DesktopModeController::showDesktopApps);
controller -> 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];

View File

@@ -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<Int>()
private val visibleTasks = ArraySet<Int>()
/** 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<Int> = ArraySet(),
val visibleTasks: ArraySet<Int> = ArraySet(),
)
// Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
private val freeformTasksInZOrder = mutableListOf<Int>()
private val activeTasksListeners = ArraySet<ActiveTasksListener>()
@@ -47,9 +53,22 @@ class DesktopModeTaskRepository {
private var desktopGestureExclusionListener: Consumer<Region>? = 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<DisplayData>() {
/**
* 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<Int> {
return ArraySet(activeTasks)
fun getActiveTasks(displayId: Int): ArraySet<Int> {
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<Int> {
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) {}
}
}

View File

@@ -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]

View File

@@ -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);
});
}
}

View File

@@ -245,7 +245,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@Override
public void onActiveTasksChanged() {
public void onActiveTasksChanged(int displayId) {
notifyRecentTasksChanged();
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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<RunningTaskInfo>()
@@ -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) {

View File

@@ -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)

View File

@@ -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