Merge "Store desktop tasks per display id" into udc-dev

This commit is contained in:
Ats Jenk
2023-04-27 18:19:08 +00:00
committed by Android (Google) Code Review
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