Merge "Add support to move task to another display" into udc-dev

This commit is contained in:
Ats Jenk
2023-05-01 23:39:27 +00:00
committed by Android (Google) Code Review
6 changed files with 141 additions and 0 deletions

View File

@@ -180,6 +180,17 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
applyConfigChangesToContext(displayAreaInfo);
}
/**
* Returns the list of display ids that are tracked by a {@link DisplayAreaInfo}
*/
public int[] getDisplayIds() {
int[] displayIds = new int[mDisplayAreasInfo.size()];
for (int i = 0; i < mDisplayAreasInfo.size(); i++) {
displayIds[i] = mDisplayAreasInfo.keyAt(i);
}
return displayIds;
}
/**
* Returns the {@link DisplayAreaInfo} of the {@link DisplayAreaInfo#displayId}.
*/

View File

@@ -48,6 +48,12 @@ public class DesktopModeStatus {
private static final boolean IS_VEILED_RESIZE_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_veiled_resizing", true);
/**
* Flag to indicate is moving task to another display is enabled.
*/
public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_change_display", false);
/**
* Return {@code true} if desktop mode support is enabled
*/

View File

@@ -234,6 +234,69 @@ class DesktopTasksController(
}
}
/**
* Move task to the next display.
*
* Queries all current known display ids and sorts them in ascending order. Then iterates
* through the list and looks for the display id that is larger than the display id for
* the passed in task. If a display with a higher id is not found, iterates through the list and
* finds the first display id that is not the display id for the passed in task.
*
* If a display matching the above criteria is found, re-parents the task to that display.
* No-op if no such display is found.
*/
fun moveToNextDisplay(taskId: Int) {
val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
if (task == null) {
ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
return
}
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d",
taskId, task.displayId)
val displayIds = rootTaskDisplayAreaOrganizer.displayIds.sorted()
// Get the first display id that is higher than current task display id
var newDisplayId = displayIds.firstOrNull { displayId -> displayId > task.displayId }
if (newDisplayId == null) {
// No display with a higher id, get the first display id that is not the task display id
newDisplayId = displayIds.firstOrNull { displayId -> displayId < task.displayId }
}
if (newDisplayId == null) {
ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found")
return
}
moveToDisplay(task, newDisplayId)
}
/**
* Move [task] to display with [displayId].
*
* No-op if task is already on that display per [RunningTaskInfo.displayId].
*/
private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d",
task.taskId, displayId)
if (task.displayId == displayId) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
return
}
val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
if (displayAreaInfo == null) {
ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found")
return
}
val wct = WindowContainerTransaction()
wct.reparent(task.token, displayAreaInfo.token, true /* onTop */)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
}
/**
* Get windowing move for a given `taskId`
*

View File

@@ -326,6 +326,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
decoration.closeHandleMenu();
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
} else if (id == R.id.select_button) {
if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) {
// TODO(b/278084491): dev option to enable display switching
// remove when select is implemented
mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
decoration.closeHandleMenu();
}
}
}

View File

@@ -187,6 +187,8 @@ class HandleMenu {
final View moreActionsPillView = mMoreActionsPill.mWindowViewHost.getView();
final Button closeBtn = moreActionsPillView.findViewById(R.id.close_button);
closeBtn.setOnClickListener(mOnClickListener);
final Button selectBtn = moreActionsPillView.findViewById(R.id.select_button);
selectBtn.setOnClickListener(mOnClickListener);
}
/**

View File

@@ -31,6 +31,7 @@ import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.DisplayAreaInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
@@ -343,6 +344,57 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
}
@Test
fun moveToNextDisplay_noOtherDisplays() {
whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
controller.moveToNextDisplay(task.taskId)
verifyWCTNotExecuted()
}
@Test
fun moveToNextDisplay_moveFromFirstToSecondDisplay() {
// Set up two display ids
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
// Create a mock for the target display area: second display
val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
.thenReturn(secondDisplayArea)
val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
controller.moveToNextDisplay(task.taskId)
with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
assertThat(hierarchyOps).hasSize(1)
assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
assertThat(hierarchyOps[0].isReparent).isTrue()
assertThat(hierarchyOps[0].newParent).isEqualTo(secondDisplayArea.token.asBinder())
assertThat(hierarchyOps[0].toTop).isTrue()
}
}
@Test
fun moveToNextDisplay_moveFromSecondToFirstDisplay() {
// Set up two display ids
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
// Create a mock for the target display area: default display
val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
.thenReturn(defaultDisplayArea)
val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
controller.moveToNextDisplay(task.taskId)
with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
assertThat(hierarchyOps).hasSize(1)
assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
assertThat(hierarchyOps[0].isReparent).isTrue()
assertThat(hierarchyOps[0].newParent).isEqualTo(defaultDisplayArea.token.asBinder())
assertThat(hierarchyOps[0].toTop).isTrue()
}
}
@Test
fun getTaskWindowingMode() {
val fullscreenTask = setUpFullscreenTask()