summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author dakinola <dakinola@google.com> 2024-07-22 10:28:54 +0000
committer Daniel Akinola <dakinola@google.com> 2024-08-01 16:50:33 +0000
commite2832fdc0c98a3c50d0f2b1be9a5de83a04f08fc (patch)
tree20c95fed354420e704cff656ffa8b913ec8fd3ed
parent2e7481d198e55525e7b8d4bcce24eb03fa7aa024 (diff)
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
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt152
3 files changed, 179 insertions, 10 deletions
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<WindowDecorLin
// transaction (that applies task crop) is synced with the buffer transaction (that draws
// the View). Both will be shown on screen at the same, whereas applying them independently
// causes flickering. See b/270202228.
- final boolean applyTransactionOnDraw =
- taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ final boolean applyTransactionOnDraw = taskInfo.isFreeform();
relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskPositionAndCrop);
if (!applyTransactionOnDraw) {
t.apply();
@@ -324,7 +323,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ if (taskInfo.isFreeform()) {
// The Task is in Freeform mode -> 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<WindowDecorLin
}
private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo) {
- final boolean isFreeform =
- taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
- return isFreeform && taskInfo.isResizeable;
+ return taskInfo.isFreeform() && taskInfo.isResizeable;
}
private void updateMaximizeMenu(SurfaceControl.Transaction startT) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 61c7080789bf..bbf42b5fb1a1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -21,6 +21,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.ComponentName
@@ -51,11 +52,13 @@ import android.view.InputMonitor
import android.view.InsetsSource
import android.view.InsetsState
import android.view.KeyEvent
+import android.view.Surface
import android.view.SurfaceControl
import android.view.SurfaceView
import android.view.View
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
+import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
@@ -69,6 +72,7 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
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
@@ -110,6 +114,7 @@ import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doNothing
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -166,6 +171,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var shellInit: ShellInit
private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
+ private lateinit var displayChangingListener: DisplayChangeController.OnDisplayChangingListener
private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
@Before
@@ -174,6 +180,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockitoSession()
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus::class.java)
+ .spyStatic(DragPositioningCallbackUtility::class.java)
.startMocking()
doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(Mockito.any()) }
@@ -218,10 +225,17 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
shellInit.init()
- val listenerCaptor =
- argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
- verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture())
- desktopModeOnInsetsChangedListener = listenerCaptor.firstValue
+ val insetListenerCaptor =
+ argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
+ verify(displayInsetsController)
+ .addInsetsChangedListener(anyInt(), insetListenerCaptor.capture())
+ desktopModeOnInsetsChangedListener = insetListenerCaptor.firstValue
+
+ val displayChangingListenerCaptor =
+ argumentCaptor<DisplayChangeController.OnDisplayChangingListener>()
+ 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<WindowContainerTransaction>()
+
+ 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<WindowContainerTransaction>()
+ 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<WindowContainerTransaction>()
+ 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<WindowContainerTransaction>()
+ 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<WindowContainerTransaction>()
+ 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<Function0<Unit>> =
@@ -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
}