Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9214f324 authored by Vania Desmonda's avatar Vania Desmonda
Browse files

Restrict maximum window size to the stable bounds of the display when

resizing a window in desktop windowing mode.

Fixes: 269663649
Flag: com.android.window.flags.enable_desktop_windowing_size_constraints
Test: manual test, atest CtsWindowManagerDeviceAnimations:ManifestLayoutTests and WMShellUnitTests:DragPositioningCallbackUtilityTest
Change-Id: I48ec0a8d7a85e70ebab0fb24cf8b78af44d3aa8a
parent f6ec228d
Loading
Loading
Loading
Loading
+32 −4
Original line number Original line Diff line number Diff line
@@ -28,6 +28,8 @@ import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.SurfaceControl;


import androidx.annotation.NonNull;

import com.android.window.flags.Flags;
import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayController;
@@ -106,13 +108,15 @@ public class DragPositioningCallbackUtility {
            repositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom)
            repositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom)
                    ? candidateBottom : oldBottom;
                    ? candidateBottom : oldBottom;
        }
        }
        // If width or height are negative or less than the minimum width or height, revert the
        // If width or height are negative or exceeding the width or height constraints, revert the
        // respective bounds to use previous bound dimensions.
        // respective bounds to use previous bound dimensions.
        if (repositionTaskBounds.width() < getMinWidth(displayController, windowDecoration)) {
        if (isExceedingWidthConstraint(repositionTaskBounds, stableBounds, displayController,
                windowDecoration)) {
            repositionTaskBounds.right = oldRight;
            repositionTaskBounds.right = oldRight;
            repositionTaskBounds.left = oldLeft;
            repositionTaskBounds.left = oldLeft;
        }
        }
        if (repositionTaskBounds.height() < getMinHeight(displayController, windowDecoration)) {
        if (isExceedingHeightConstraint(repositionTaskBounds, stableBounds, displayController,
                windowDecoration)) {
            repositionTaskBounds.top = oldTop;
            repositionTaskBounds.top = oldTop;
            repositionTaskBounds.bottom = oldBottom;
            repositionTaskBounds.bottom = oldBottom;
        }
        }
@@ -174,6 +178,30 @@ public class DragPositioningCallbackUtility {
        return result;
        return result;
    }
    }


    private static boolean isExceedingWidthConstraint(@NonNull Rect repositionTaskBounds,
            Rect maxResizeBounds, DisplayController displayController,
            WindowDecoration windowDecoration) {
        // Check if width is less than the minimum width constraint.
        if (repositionTaskBounds.width() < getMinWidth(displayController, windowDecoration)) {
            return true;
        }
        // Check if width is more than the maximum resize bounds on desktop windowing mode.
        return isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)
                && repositionTaskBounds.width() > maxResizeBounds.width();
    }

    private static boolean isExceedingHeightConstraint(@NonNull Rect repositionTaskBounds,
            Rect maxResizeBounds, DisplayController displayController,
            WindowDecoration windowDecoration) {
        // Check if height is less than the minimum height constraint.
        if (repositionTaskBounds.height() < getMinHeight(displayController, windowDecoration)) {
            return true;
        }
        // Check if height is more than the maximum resize bounds on desktop windowing mode.
        return isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext)
                && repositionTaskBounds.height() > maxResizeBounds.height();
    }

    private static float getMinWidth(DisplayController displayController,
    private static float getMinWidth(DisplayController displayController,
            WindowDecoration windowDecoration) {
            WindowDecoration windowDecoration) {
        return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinWidth(displayController,
        return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinWidth(displayController,
+58 −0
Original line number Original line Diff line number Diff line
@@ -21,7 +21,9 @@ import android.content.res.Resources
import android.graphics.PointF
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.Rect
import android.os.IBinder
import android.os.IBinder
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.Display
import android.window.WindowContainerToken
import android.window.WindowContainerToken
@@ -36,6 +38,7 @@ import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertTrue
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mock
@@ -53,21 +56,32 @@ import org.mockito.MockitoAnnotations
class DragPositioningCallbackUtilityTest {
class DragPositioningCallbackUtilityTest {
    @Mock
    @Mock
    private lateinit var mockWindowDecoration: WindowDecoration<*>
    private lateinit var mockWindowDecoration: WindowDecoration<*>

    @Mock
    @Mock
    private lateinit var taskToken: WindowContainerToken
    private lateinit var taskToken: WindowContainerToken

    @Mock
    @Mock
    private lateinit var taskBinder: IBinder
    private lateinit var taskBinder: IBinder

    @Mock
    @Mock
    private lateinit var mockDisplayController: DisplayController
    private lateinit var mockDisplayController: DisplayController

    @Mock
    @Mock
    private lateinit var mockDisplayLayout: DisplayLayout
    private lateinit var mockDisplayLayout: DisplayLayout

    @Mock
    @Mock
    private lateinit var mockDisplay: Display
    private lateinit var mockDisplay: Display

    @Mock
    @Mock
    private lateinit var mockContext: Context
    private lateinit var mockContext: Context

    @Mock
    @Mock
    private lateinit var mockResources: Resources
    private lateinit var mockResources: Resources


    @JvmField
    @Rule
    val setFlagsRule = SetFlagsRule()

    @Before
    @Before
    fun setup() {
    fun setup() {
        MockitoAnnotations.initMocks(this)
        MockitoAnnotations.initMocks(this)
@@ -323,6 +337,49 @@ class DragPositioningCallbackUtilityTest {
        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom - 50)
        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom - 50)
    }
    }


    @Test
    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
    fun testChangeBounds_windowSizeExceedsStableBounds_shouldBeAllowedToChangeBounds() {
        val startingPoint =
            PointF(OFF_CENTER_STARTING_BOUNDS.right.toFloat(),
                OFF_CENTER_STARTING_BOUNDS.bottom.toFloat())
        val repositionTaskBounds = Rect(OFF_CENTER_STARTING_BOUNDS)
        // Increase height and width by STABLE_BOUNDS. Subtract by 5px so that it doesn't reach
        // the disallowed drag area.
        val offset = 5
        val newX = STABLE_BOUNDS.right.toFloat() - offset
        val newY = STABLE_BOUNDS.bottom.toFloat() - offset
        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)

        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
            repositionTaskBounds, OFF_CENTER_STARTING_BOUNDS, STABLE_BOUNDS, delta,
            mockDisplayController, mockWindowDecoration)
        assertThat(repositionTaskBounds.width()).isGreaterThan(STABLE_BOUNDS.right)
        assertThat(repositionTaskBounds.height()).isGreaterThan(STABLE_BOUNDS.bottom)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
    fun testChangeBoundsInDesktopMode_windowSizeExceedsStableBounds_shouldBeLimitedToDisplaySize() {
        whenever(DesktopModeStatus.canEnterDesktopMode(mockContext)).thenReturn(true)
        val startingPoint =
            PointF(OFF_CENTER_STARTING_BOUNDS.right.toFloat(),
                OFF_CENTER_STARTING_BOUNDS.bottom.toFloat())
        val repositionTaskBounds = Rect(OFF_CENTER_STARTING_BOUNDS)
        // Increase height and width by STABLE_BOUNDS. Subtract by 5px so that it doesn't reach
        // the disallowed drag area.
        val offset = 5
        val newX = STABLE_BOUNDS.right.toFloat() - offset
        val newY = STABLE_BOUNDS.bottom.toFloat() - offset
        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)

        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
            repositionTaskBounds, OFF_CENTER_STARTING_BOUNDS, STABLE_BOUNDS, delta,
            mockDisplayController, mockWindowDecoration)
        assertThat(repositionTaskBounds.width()).isLessThan(STABLE_BOUNDS.right)
        assertThat(repositionTaskBounds.height()).isLessThan(STABLE_BOUNDS.bottom)
    }

    private fun initializeTaskInfo(taskMinWidth: Int = MIN_WIDTH, taskMinHeight: Int = MIN_HEIGHT) {
    private fun initializeTaskInfo(taskMinWidth: Int = MIN_WIDTH, taskMinHeight: Int = MIN_HEIGHT) {
        mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
        mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
            taskId = TASK_ID
            taskId = TASK_ID
@@ -347,6 +404,7 @@ class DragPositioningCallbackUtilityTest {
        private const val NAVBAR_HEIGHT = 50
        private const val NAVBAR_HEIGHT = 50
        private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
        private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
        private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
        private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
        private val OFF_CENTER_STARTING_BOUNDS = Rect(-100, -100, 10, 10)
        private val DISALLOWED_RESIZE_AREA = Rect(
        private val DISALLOWED_RESIZE_AREA = Rect(
            DISPLAY_BOUNDS.left,
            DISPLAY_BOUNDS.left,
            DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
            DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+9 −0
Original line number Original line Diff line number Diff line
@@ -17,6 +17,8 @@ package com.android.wm.shell.windowdecor


import android.app.ActivityManager
import android.app.ActivityManager
import android.app.WindowConfiguration
import android.app.WindowConfiguration
import android.content.Context
import android.content.res.Resources
import android.graphics.Point
import android.graphics.Point
import android.graphics.Rect
import android.graphics.Rect
import android.os.IBinder
import android.os.IBinder
@@ -98,6 +100,10 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
    private lateinit var mockFinishCallback: TransitionFinishCallback
    private lateinit var mockFinishCallback: TransitionFinishCallback
    @Mock
    @Mock
    private lateinit var mockTransitions: Transitions
    private lateinit var mockTransitions: Transitions
    @Mock
    private lateinit var mockContext: Context
    @Mock
    private lateinit var mockResources: Resources


    private lateinit var taskPositioner: VeiledResizeTaskPositioner
    private lateinit var taskPositioner: VeiledResizeTaskPositioner


@@ -105,6 +111,9 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
    fun setUp() {
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        MockitoAnnotations.initMocks(this)


        mockDesktopWindowDecoration.mDisplay = mockDisplay
        mockDesktopWindowDecoration.mDecorWindowContext = mockContext
        whenever(mockContext.getResources()).thenReturn(mockResources)
        whenever(taskToken.asBinder()).thenReturn(taskBinder)
        whenever(taskToken.asBinder()).thenReturn(taskBinder)
        whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
        whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
        whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
        whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)