From e2832fdc0c98a3c50d0f2b1be9a5de83a04f08fc Mon Sep 17 00:00:00 2001 From: dakinola Date: Mon, 22 Jul 2024 10:28:54 +0000 Subject: Snap desktop window to valid area on display rotate Updated DesktopModeWindowDecorViewModel to listen to display rotations, and update the all task bounds if the task would end up unreachable on the rotated display bounds Bug: 348389314 Test: atest DesktopModeWindowDecorViewModelTests Test: manual testing Flag: EXEMPT bugfix Change-Id: Ia8ec5782d75bce4d94a44c9d8a8575b2889c9282 --- .../DesktopModeWindowDecorViewModel.java | 28 ++++ .../windowdecor/DesktopModeWindowDecoration.java | 9 +- .../DesktopModeWindowDecorViewModelTests.kt | 152 ++++++++++++++++++++- 3 files changed, 179 insertions(+), 10 deletions(-) (limited to 'libs') diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 81942e8ef59b..d68c018267a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -89,6 +89,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; +import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; @@ -175,6 +176,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private boolean mInImmersiveMode; private final String mSysUIPackageName; + private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener; private final ISystemGestureExclusionListener mGestureExclusionListener = new ISystemGestureExclusionListener.Stub() { @Override @@ -287,6 +289,31 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mSysUIPackageName = mContext.getResources().getString( com.android.internal.R.string.config_systemUi); mInteractionJankMonitor = interactionJankMonitor; + mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> { + DesktopModeWindowDecoration decoration; + RunningTaskInfo taskInfo; + for (int i = 0; i < mWindowDecorByTaskId.size(); i++) { + decoration = mWindowDecorByTaskId.valueAt(i); + if (decoration == null) { + continue; + } else { + taskInfo = decoration.mTaskInfo; + } + + // Check if display has been rotated between portrait & landscape + if (displayId == taskInfo.displayId && taskInfo.isFreeform() + && (fromRotation % 2 != toRotation % 2)) { + // Check if the task bounds on the rotated display will be out of bounds. + // If so, then update task bounds to be within reachable area. + final Rect taskBounds = new Rect( + taskInfo.configuration.windowConfiguration.getBounds()); + if (DragPositioningCallbackUtility.snapTaskBoundsIfNecessary( + taskBounds, decoration.calculateValidDragArea())) { + t.setBounds(taskInfo.token, taskBounds); + } + } + } + }; shellInit.addInitCallback(this::onInit, this); } @@ -298,6 +325,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { new DesktopModeOnInsetsChangedListener()); mDesktopTasksController.setOnTaskResizeAnimationListener( new DesktopModeOnTaskResizeAnimationListener()); + mDisplayController.addDisplayChangingController(mOnDisplayChangingListener); try { mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener, mContext.getDisplayId()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 24fb97197f39..d70e225b9a9a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -312,8 +312,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration show its header in sync since it's an integral part // of the window itself - a delayed header might cause bad UX. relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw, @@ -524,9 +523,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration() - verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture()) - desktopModeOnInsetsChangedListener = listenerCaptor.firstValue + val insetListenerCaptor = + argumentCaptor() + verify(displayInsetsController) + .addInsetsChangedListener(anyInt(), insetListenerCaptor.capture()) + desktopModeOnInsetsChangedListener = insetListenerCaptor.firstValue + + val displayChangingListenerCaptor = + argumentCaptor() + verify(mockDisplayController) + .addDisplayChangingController(displayChangingListenerCaptor.capture()) + displayChangingListener = displayChangingListenerCaptor.firstValue } @After @@ -786,6 +800,135 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { }) } + @Test + fun testOnDisplayRotation_tasksOutOfValidArea_taskBoundsUpdated() { + val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM) + val secondTask = + createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM) + val thirdTask = + createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM) + + doReturn(true).`when` { + DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(any(), any()) + } + setUpMockDecorationsForTasks(task, secondTask, thirdTask) + + onTaskOpening(task) + onTaskOpening(secondTask) + onTaskOpening(thirdTask) + + val wct = mock() + + displayChangingListener.onDisplayChange( + task.displayId, Surface.ROTATION_0, Surface.ROTATION_90, null, wct + ) + + verify(wct).setBounds(eq(task.token), any()) + verify(wct).setBounds(eq(secondTask.token), any()) + verify(wct).setBounds(eq(thirdTask.token), any()) + } + + @Test + fun testOnDisplayRotation_taskInValidArea_taskBoundsNotUpdated() { + val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM) + val secondTask = + createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM) + val thirdTask = + createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM) + + doReturn(false).`when` { + DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(any(), any()) + } + setUpMockDecorationsForTasks(task, secondTask, thirdTask) + + onTaskOpening(task) + onTaskOpening(secondTask) + onTaskOpening(thirdTask) + + val wct = mock() + displayChangingListener.onDisplayChange( + task.displayId, Surface.ROTATION_0, Surface.ROTATION_90, null, wct + ) + + verify(wct, never()).setBounds(eq(task.token), any()) + verify(wct, never()).setBounds(eq(secondTask.token), any()) + verify(wct, never()).setBounds(eq(thirdTask.token), any()) + } + + @Test + fun testOnDisplayRotation_sameOrientationRotation_taskBoundsNotUpdated() { + val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM) + val secondTask = + createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM) + val thirdTask = + createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM) + + setUpMockDecorationsForTasks(task, secondTask, thirdTask) + + onTaskOpening(task) + onTaskOpening(secondTask) + onTaskOpening(thirdTask) + + val wct = mock() + displayChangingListener.onDisplayChange( + task.displayId, Surface.ROTATION_0, Surface.ROTATION_180, null, wct + ) + + verify(wct, never()).setBounds(eq(task.token), any()) + verify(wct, never()).setBounds(eq(secondTask.token), any()) + verify(wct, never()).setBounds(eq(thirdTask.token), any()) + } + + @Test + fun testOnDisplayRotation_differentDisplayId_taskBoundsNotUpdated() { + val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM) + val secondTask = createTask(displayId = -2, windowingMode = WINDOWING_MODE_FREEFORM) + val thirdTask = createTask(displayId = -3, windowingMode = WINDOWING_MODE_FREEFORM) + + doReturn(true).`when` { + DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(any(), any()) + } + setUpMockDecorationsForTasks(task, secondTask, thirdTask) + + onTaskOpening(task) + onTaskOpening(secondTask) + onTaskOpening(thirdTask) + + val wct = mock() + displayChangingListener.onDisplayChange( + task.displayId, Surface.ROTATION_0, Surface.ROTATION_90, null, wct + ) + + verify(wct).setBounds(eq(task.token), any()) + verify(wct, never()).setBounds(eq(secondTask.token), any()) + verify(wct, never()).setBounds(eq(thirdTask.token), any()) + } + + @Test + fun testOnDisplayRotation_nonFreeformTask_taskBoundsNotUpdated() { + val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM) + val secondTask = createTask(displayId = -2, windowingMode = WINDOWING_MODE_FULLSCREEN) + val thirdTask = createTask(displayId = -3, windowingMode = WINDOWING_MODE_PINNED) + + doReturn(true).`when` { + DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(any(), any()) + } + setUpMockDecorationsForTasks(task, secondTask, thirdTask) + + onTaskOpening(task) + onTaskOpening(secondTask) + onTaskOpening(thirdTask) + + val wct = mock() + displayChangingListener.onDisplayChange( + task.displayId, Surface.ROTATION_0, Surface.ROTATION_90, null, wct + ) + + verify(wct).setBounds(eq(task.token), any()) + verify(wct, never()).setBounds(eq(secondTask.token), any()) + verify(wct, never()).setBounds(eq(thirdTask.token), any()) + } + private fun createOpenTaskDecoration( @WindowingMode windowingMode: Int, onMaxOrRestoreListenerCaptor: ArgumentCaptor> = @@ -864,6 +1007,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever(mockSplitScreenController.isTaskInSplitScreen(task.taskId)) .thenReturn(true) } + whenever(decoration.calculateValidDragArea()).thenReturn(Rect(0, 60, 2560, 1600)) return decoration } -- cgit v1.2.3-59-g8ed1b