diff options
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 } |