diff options
7 files changed, 531 insertions, 111 deletions
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 6d5bf6233366..ca401c3b1ec6 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -513,8 +513,20 @@ <!-- The size of the icon shown in the resize veil. --> <dimen name="desktop_mode_resize_veil_icon_size">96dp</dimen> + <!-- The with of the border around the app task for edge resizing, when + enable_windowing_edge_drag_resize is enabled. --> + <dimen name="desktop_mode_edge_handle">12dp</dimen> + + <!-- The original width of the border around the app task for edge resizing, when + enable_windowing_edge_drag_resize is disabled. --> <dimen name="freeform_resize_handle">15dp</dimen> + <!-- The size of the corner region for drag resizing with touch, when a larger touch region is + appropriate. Applied when enable_windowing_edge_drag_resize is enabled. --> + <dimen name="desktop_mode_corner_resize_large">48dp</dimen> + + <!-- The original size of the corner region for darg resizing, when + enable_windowing_edge_drag_resize is disabled. --> <dimen name="freeform_resize_corner">44dp</dimen> <!-- The width of the area at the sides of the screen where a freeform task will transition to diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 3c98e703ec21..43fd32ba1750 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -16,12 +16,17 @@ package com.android.wm.shell.windowdecor; +import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize; +import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize; +import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize; + import android.annotation.NonNull; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; @@ -234,13 +239,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL .getScaledTouchSlop(); mDragDetector.setTouchSlop(touchSlop); - final int resizeHandle = mResult.mRootView.getResources() - .getDimensionPixelSize(R.dimen.freeform_resize_handle); - final int resizeCorner = mResult.mRootView.getResources() - .getDimensionPixelSize(R.dimen.freeform_resize_corner); + final Resources res = mResult.mRootView.getResources(); mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */, - new Size(mResult.mWidth, mResult.mHeight), resizeHandle, resizeCorner), - touchSlop); + new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res), + getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop); } /** 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 296fcb60b7a1..2bbe530fbaf6 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 @@ -24,6 +24,9 @@ import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT; +import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize; +import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize; +import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize; import android.annotation.NonNull; import android.app.ActivityManager; @@ -288,17 +291,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin .getScaledTouchSlop(); mDragDetector.setTouchSlop(touchSlop); - final int resizeHandle = mResult.mRootView.getResources() - .getDimensionPixelSize(R.dimen.freeform_resize_handle); - final int resizeCorner = mResult.mRootView.getResources() - .getDimensionPixelSize(R.dimen.freeform_resize_corner); - // If either task geometry or position have changed, update this task's // exclusion region listener + final Resources res = mResult.mRootView.getResources(); if (mDragResizeListener.setGeometry( new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius, - new Size(mResult.mWidth, mResult.mHeight), resizeHandle, resizeCorner), - touchSlop) || !mTaskInfo.positionInParent.equals(mPositionInParent)) { + new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res), + getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop) + || !mTaskInfo.positionInParent.equals(mPositionInParent)) { updateExclusionRegion(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java index 8ce2d6d6d092..421ffd929fb2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java @@ -23,6 +23,9 @@ import android.graphics.Rect; * Callback called when receiving drag-resize or drag-move related input events. */ public interface DragPositioningCallback { + /** + * Indicates the direction of resizing. May be combined together to indicate a diagonal drag. + */ @IntDef(flag = true, value = { CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM }) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java index 2884d9f2a536..eafb56995db7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java @@ -18,35 +18,38 @@ package com.android.wm.shell.windowdecor; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; +import static com.android.window.flags.Flags.enableWindowingEdgeDragResize; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP; +import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED; import android.annotation.NonNull; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.util.Size; import android.view.MotionEvent; +import com.android.wm.shell.R; + import java.util.Objects; /** - * Geometry for an input region for a particular window. + * Geometry for a drag resize region for a particular window. */ final class DragResizeWindowGeometry { private final int mTaskCornerRadius; private final Size mTaskSize; // The size of the handle applied to the edges of the window, for the user to drag resize. private final int mResizeHandleThickness; - // The size of the rectangle applied to the corners of the window, for the user to drag resize. - private final int mCornerSize; - // The bounds for the corner drag region, which can resize the task in two directions. - private final @NonNull Rect mLeftTopCornerBounds; - private final @NonNull Rect mRightTopCornerBounds; - private final @NonNull Rect mLeftBottomCornerBounds; - private final @NonNull Rect mRightBottomCornerBounds; + // The task corners to permit drag resizing with a course input, such as touch. + + private final @NonNull TaskCorners mLargeTaskCorners; + // The task corners to permit drag resizing with a fine input, such as stylus or cursor. + private final @NonNull TaskCorners mFineTaskCorners; // The bounds for each edge drag region, which can resize the task in one direction. private final @NonNull Rect mTopEdgeBounds; private final @NonNull Rect mLeftEdgeBounds; @@ -54,34 +57,13 @@ final class DragResizeWindowGeometry { private final @NonNull Rect mBottomEdgeBounds; DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize, - int resizeHandleThickness, int cornerSize) { + int resizeHandleThickness, int fineCornerSize, int largeCornerSize) { mTaskCornerRadius = taskCornerRadius; mTaskSize = taskSize; mResizeHandleThickness = resizeHandleThickness; - mCornerSize = cornerSize; - - // Save touch areas in each corner. - final int cornerRadius = mCornerSize / 2; - mLeftTopCornerBounds = new Rect( - -cornerRadius, - -cornerRadius, - cornerRadius, - cornerRadius); - mRightTopCornerBounds = new Rect( - mTaskSize.getWidth() - cornerRadius, - -cornerRadius, - mTaskSize.getWidth() + cornerRadius, - cornerRadius); - mLeftBottomCornerBounds = new Rect( - -cornerRadius, - mTaskSize.getHeight() - cornerRadius, - cornerRadius, - mTaskSize.getHeight() + cornerRadius); - mRightBottomCornerBounds = new Rect( - mTaskSize.getWidth() - cornerRadius, - mTaskSize.getHeight() - cornerRadius, - mTaskSize.getWidth() + cornerRadius, - mTaskSize.getHeight() + cornerRadius); + + mLargeTaskCorners = new TaskCorners(mTaskSize, largeCornerSize); + mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize); // Save touch areas for each edge. mTopEdgeBounds = new Rect( @@ -107,6 +89,31 @@ final class DragResizeWindowGeometry { } /** + * Returns the resource value to use for the resize handle on the edge of the window. + */ + static int getResizeEdgeHandleSize(@NonNull Resources res) { + return enableWindowingEdgeDragResize() + ? res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle) + : res.getDimensionPixelSize(R.dimen.freeform_resize_handle); + } + + /** + * Returns the resource value to use for course input, such as touch, that benefits from a large + * square on each of the window's corners. + */ + static int getLargeResizeCornerSize(@NonNull Resources res) { + return res.getDimensionPixelSize(R.dimen.desktop_mode_corner_resize_large); + } + + /** + * Returns the resource value to use for fine input, such as stylus, that can use a smaller + * square on each of the window's corners. + */ + static int getFineResizeCornerSize(@NonNull Resources res) { + return res.getDimensionPixelSize(R.dimen.freeform_resize_corner); + } + + /** * Returns the size of the task this geometry is calculated for. */ @NonNull Size getTaskSize() { @@ -116,7 +123,7 @@ final class DragResizeWindowGeometry { /** * Returns the union of all regions that can be touched for drag resizing; the corners window - * edges. + * and window edges. */ void union(@NonNull Region region) { // Apply the edge resize regions. @@ -125,11 +132,14 @@ final class DragResizeWindowGeometry { region.union(mRightEdgeBounds); region.union(mBottomEdgeBounds); - // Apply the corners as well. - region.union(mLeftTopCornerBounds); - region.union(mRightTopCornerBounds); - region.union(mLeftBottomCornerBounds); - region.union(mRightBottomCornerBounds); + if (enableWindowingEdgeDragResize()) { + // Apply the corners as well for the larger corners, to ensure we capture all possible + // touches. + mLargeTaskCorners.union(region); + } else { + // Only apply fine corners for the legacy approach. + mFineTaskCorners.union(region); + } } /** @@ -143,27 +153,39 @@ final class DragResizeWindowGeometry { * Returns if this MotionEvent should be handled, based on its source and position. */ boolean shouldHandleEvent(@NonNull MotionEvent e, boolean isTouch, @NonNull Point offset) { - boolean result; final float x = e.getX(0) + offset.x; final float y = e.getY(0) + offset.y; - if (isTouch) { - result = isInCornerBounds(x, y); + + if (enableWindowingEdgeDragResize()) { + // First check if touch falls within a corner. + // Large corner bounds are used for course input like touch, otherwise fine bounds. + boolean result = isTouch + ? isInCornerBounds(mLargeTaskCorners, x, y) + : isInCornerBounds(mFineTaskCorners, x, y); + // Check if touch falls within the edge resize handle, since edge resizing can apply + // for any input source. + if (!result) { + result = isInEdgeResizeBounds(x, y); + } + return result; } else { - result = isInResizeHandleBounds(x, y); + // Legacy uses only fine corners for touch, and edges only for non-touch input. + return isTouch + ? isInCornerBounds(mFineTaskCorners, x, y) + : isInEdgeResizeBounds(x, y); } - return result; } private boolean isTouchEvent(@NonNull MotionEvent e) { return (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; } - private boolean isInCornerBounds(float xf, float yf) { - return calculateCornersCtrlType(xf, yf) != 0; + private boolean isInCornerBounds(TaskCorners corners, float xf, float yf) { + return corners.calculateCornersCtrlType(xf, yf) != 0; } - private boolean isInResizeHandleBounds(float x, float y) { - return calculateResizeHandlesCtrlType(x, y) != 0; + private boolean isInEdgeResizeBounds(float x, float y) { + return calculateEdgeResizeCtrlType(x, y) != 0; } /** @@ -172,36 +194,31 @@ final class DragResizeWindowGeometry { */ @DragPositioningCallback.CtrlType int calculateCtrlType(boolean isTouch, float x, float y) { - if (isTouch) { - return calculateCornersCtrlType(x, y); - } - return calculateResizeHandlesCtrlType(x, y); - } - - @DragPositioningCallback.CtrlType - private int calculateCornersCtrlType(float x, float y) { - int xi = (int) x; - int yi = (int) y; - if (mLeftTopCornerBounds.contains(xi, yi)) { - return CTRL_TYPE_LEFT | CTRL_TYPE_TOP; - } - if (mLeftBottomCornerBounds.contains(xi, yi)) { - return CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM; - } - if (mRightTopCornerBounds.contains(xi, yi)) { - return CTRL_TYPE_RIGHT | CTRL_TYPE_TOP; - } - if (mRightBottomCornerBounds.contains(xi, yi)) { - return CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM; + if (enableWindowingEdgeDragResize()) { + // First check if touch falls within a corner. + // Large corner bounds are used for course input like touch, otherwise fine bounds. + int ctrlType = isTouch + ? mLargeTaskCorners.calculateCornersCtrlType(x, y) + : mFineTaskCorners.calculateCornersCtrlType(x, y); + // Check if touch falls within the edge resize handle, since edge resizing can apply + // for any input source. + if (ctrlType == CTRL_TYPE_UNDEFINED) { + ctrlType = calculateEdgeResizeCtrlType(x, y); + } + return ctrlType; + } else { + // Legacy uses only fine corners for touch, and edges only for non-touch input. + return isTouch + ? mFineTaskCorners.calculateCornersCtrlType(x, y) + : calculateEdgeResizeCtrlType(x, y); } - return 0; } @DragPositioningCallback.CtrlType - private int calculateResizeHandlesCtrlType(float x, float y) { - int ctrlType = 0; + private int calculateEdgeResizeCtrlType(float x, float y) { + int ctrlType = CTRL_TYPE_UNDEFINED; // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with - // sides will use the bounds specified and not go into task bounds. + // sides will use the bounds specified in setGeometry and not go into task bounds. if (x < mTaskCornerRadius) { ctrlType |= CTRL_TYPE_LEFT; } @@ -214,19 +231,20 @@ final class DragResizeWindowGeometry { if (y > mTaskSize.getHeight() - mTaskCornerRadius) { ctrlType |= CTRL_TYPE_BOTTOM; } - // Check distances from the center if it's in one of four corners. + // If the touch is within one of the four corners, check if it is within the bounds of the + // // handle. if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0 && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) { return checkDistanceFromCenter(ctrlType, x, y); } // Otherwise, we should make sure we don't resize tasks inside task bounds. return (x < 0 || y < 0 || x >= mTaskSize.getWidth() || y >= mTaskSize.getHeight()) - ? ctrlType : 0; + ? ctrlType : CTRL_TYPE_UNDEFINED; } /** - * If corner input is not within appropriate distance of corner radius, do not use it. - * If input is not on a corner or is within valid distance, return ctrlType. + * Return {@code ctrlType} if the corner input is outside the (potentially rounded) corner of + * the task, and within the thickness of the resize handle. Otherwise, return 0. */ @DragPositioningCallback.CtrlType private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType, float x, @@ -238,7 +256,7 @@ final class DragResizeWindowGeometry { && distanceFromCenter >= mTaskCornerRadius) { return ctrlType; } - return 0; + return CTRL_TYPE_UNDEFINED; } /** @@ -286,11 +304,8 @@ final class DragResizeWindowGeometry { return this.mTaskCornerRadius == other.mTaskCornerRadius && this.mTaskSize.equals(other.mTaskSize) && this.mResizeHandleThickness == other.mResizeHandleThickness - && this.mCornerSize == other.mCornerSize - && this.mLeftTopCornerBounds.equals(other.mLeftTopCornerBounds) - && this.mRightTopCornerBounds.equals(other.mRightTopCornerBounds) - && this.mLeftBottomCornerBounds.equals(other.mLeftBottomCornerBounds) - && this.mRightBottomCornerBounds.equals(other.mRightBottomCornerBounds) + && this.mFineTaskCorners.equals(other.mFineTaskCorners) + && this.mLargeTaskCorners.equals(other.mLargeTaskCorners) && this.mTopEdgeBounds.equals(other.mTopEdgeBounds) && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds) && this.mRightEdgeBounds.equals(other.mRightEdgeBounds) @@ -303,14 +318,117 @@ final class DragResizeWindowGeometry { mTaskCornerRadius, mTaskSize, mResizeHandleThickness, - mCornerSize, - mLeftTopCornerBounds, - mRightTopCornerBounds, - mLeftBottomCornerBounds, - mRightBottomCornerBounds, + mFineTaskCorners, + mLargeTaskCorners, mTopEdgeBounds, mLeftEdgeBounds, mRightEdgeBounds, mBottomEdgeBounds); } + + /** + * Representation of the drag resize regions at the corner of the window. + */ + private static class TaskCorners { + // The size of the square applied to the corners of the window, for the user to drag + // resize. + private final int mCornerSize; + // The square for each corner. + private final @NonNull Rect mLeftTopCornerBounds; + private final @NonNull Rect mRightTopCornerBounds; + private final @NonNull Rect mLeftBottomCornerBounds; + private final @NonNull Rect mRightBottomCornerBounds; + + TaskCorners(@NonNull Size taskSize, int cornerSize) { + mCornerSize = cornerSize; + final int cornerRadius = cornerSize / 2; + mLeftTopCornerBounds = new Rect( + -cornerRadius, + -cornerRadius, + cornerRadius, + cornerRadius); + + mRightTopCornerBounds = new Rect( + taskSize.getWidth() - cornerRadius, + -cornerRadius, + taskSize.getWidth() + cornerRadius, + cornerRadius); + + mLeftBottomCornerBounds = new Rect( + -cornerRadius, + taskSize.getHeight() - cornerRadius, + cornerRadius, + taskSize.getHeight() + cornerRadius); + + mRightBottomCornerBounds = new Rect( + taskSize.getWidth() - cornerRadius, + taskSize.getHeight() - cornerRadius, + taskSize.getWidth() + cornerRadius, + taskSize.getHeight() + cornerRadius); + } + + /** + * Updates the region to include all four corners. + */ + void union(Region region) { + region.union(mLeftTopCornerBounds); + region.union(mRightTopCornerBounds); + region.union(mLeftBottomCornerBounds); + region.union(mRightBottomCornerBounds); + } + + /** + * Returns the control type based on the position of the {@code MotionEvent}'s coordinates. + */ + @DragPositioningCallback.CtrlType + int calculateCornersCtrlType(float x, float y) { + int xi = (int) x; + int yi = (int) y; + if (mLeftTopCornerBounds.contains(xi, yi)) { + return CTRL_TYPE_LEFT | CTRL_TYPE_TOP; + } + if (mLeftBottomCornerBounds.contains(xi, yi)) { + return CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM; + } + if (mRightTopCornerBounds.contains(xi, yi)) { + return CTRL_TYPE_RIGHT | CTRL_TYPE_TOP; + } + if (mRightBottomCornerBounds.contains(xi, yi)) { + return CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM; + } + return 0; + } + + @Override + public String toString() { + return "TaskCorners of size " + mCornerSize + " for the" + + " top left " + mLeftTopCornerBounds + + " top right " + mRightTopCornerBounds + + " bottom left " + mLeftBottomCornerBounds + + " bottom right " + mRightBottomCornerBounds; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (!(obj instanceof TaskCorners other)) return false; + + return this.mCornerSize == other.mCornerSize + && this.mLeftTopCornerBounds.equals(other.mLeftTopCornerBounds) + && this.mRightTopCornerBounds.equals(other.mRightTopCornerBounds) + && this.mLeftBottomCornerBounds.equals(other.mLeftBottomCornerBounds) + && this.mRightBottomCornerBounds.equals(other.mRightBottomCornerBounds); + } + + @Override + public int hashCode() { + return Objects.hash( + mCornerSize, + mLeftTopCornerBounds, + mRightTopCornerBounds, + mLeftBottomCornerBounds, + mRightBottomCornerBounds); + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index 45dc44aeccc5..13f95ccea640 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -39,7 +39,7 @@ android_test { static_libs: [ "WindowManager-Shell", "junit", - "flag-junit-base", + "flag-junit", "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", @@ -56,6 +56,8 @@ android_test { "servicestests-utils", "com_android_wm_shell_flags_lib", "guava-android-testlib", + "com.android.window.flags.window-aconfig-java", + "platform-test-annotations", ], libs: [ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java index 3e3bbe60df5b..82e5a1cd25ce 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java @@ -16,18 +16,36 @@ package com.android.wm.shell.windowdecor; +import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM; +import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT; +import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT; +import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP; +import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED; + +import static com.google.common.truth.Truth.assertThat; + +import android.annotation.NonNull; +import android.graphics.Point; +import android.graphics.Region; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.util.Size; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; + import com.google.common.testing.EqualsTester; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; /** - * Tests for {@link WindowDecoration}. + * Tests for {@link DragResizeWindowGeometry}. * * Build/Install/Run: * atest WMShellUnitTests:DragResizeWindowGeometryTests @@ -35,10 +53,26 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidTestingRunner.class) public class DragResizeWindowGeometryTests { - private static final DragResizeWindowGeometry GEOMETRY_1 = new DragResizeWindowGeometry(50, - new Size(500, 1000), 15, 40); - private static final DragResizeWindowGeometry GEOMETRY_2 = new DragResizeWindowGeometry(50, - new Size(500, 1000), 20, 40); + private static final Size TASK_SIZE = new Size(500, 1000); + private static final int TASK_CORNER_RADIUS = 10; + private static final int EDGE_RESIZE_THICKNESS = 15; + private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10; + private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10; + private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry( + TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, + LARGE_CORNER_SIZE); + // Points in the edge resize handle. Note that coordinates start from the top left. + private static final Point TOP_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2, + -EDGE_RESIZE_THICKNESS / 2); + private static final Point LEFT_EDGE_POINT = new Point(-EDGE_RESIZE_THICKNESS / 2, + TASK_SIZE.getHeight() / 2); + private static final Point RIGHT_EDGE_POINT = new Point( + TASK_SIZE.getWidth() + EDGE_RESIZE_THICKNESS / 2, TASK_SIZE.getHeight() / 2); + private static final Point BOTTOM_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2, + TASK_SIZE.getHeight() + EDGE_RESIZE_THICKNESS / 2); + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); /** * Check that both groups of objects satisfy equals/hashcode within each group, and that each @@ -47,11 +81,260 @@ public class DragResizeWindowGeometryTests { @Test public void testEqualsAndHash() { new EqualsTester() - .addEqualityGroup(GEOMETRY_1, - new DragResizeWindowGeometry(50, new Size(500, 1000), 15, 40)) .addEqualityGroup( - GEOMETRY_2, - new DragResizeWindowGeometry(50, new Size(500, 1000), 20, 40)) + GEOMETRY, + new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, + EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE)) + .addEqualityGroup( + new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, + EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE), + new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, + EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE)) + .addEqualityGroup( + new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, + EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, + LARGE_CORNER_SIZE + 5), + new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, + EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, + LARGE_CORNER_SIZE + 5)) .testEquals(); } + + @Test + public void testGetTaskSize() { + assertThat(GEOMETRY.getTaskSize()).isEqualTo(TASK_SIZE); + } + + @Test + public void testRegionUnionContainsEdges() { + Region region = new Region(); + GEOMETRY.union(region); + assertThat(region.isComplex()).isTrue(); + // Region excludes task area. Note that coordinates start from top left. + assertThat(region.contains(TASK_SIZE.getWidth() / 2, TASK_SIZE.getHeight() / 2)).isFalse(); + // Region includes edges outside the task window. + verifyVerticalEdge(region, LEFT_EDGE_POINT); + verifyHorizontalEdge(region, TOP_EDGE_POINT); + verifyVerticalEdge(region, RIGHT_EDGE_POINT); + verifyHorizontalEdge(region, BOTTOM_EDGE_POINT); + } + + private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) { + assertThat(region.contains(point.x, point.y)).isTrue(); + // Horizontally along the edge is still contained. + assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue(); + assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue(); + // Vertically along the edge is not contained. + assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse(); + assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse(); + } + + private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) { + assertThat(region.contains(point.x, point.y)).isTrue(); + // Horizontally along the edge is not contained. + assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse(); + assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse(); + // Vertically along the edge is contained. + assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue(); + assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue(); + } + + /** + * Validate that with the flag enabled, the corner resize regions are the largest size, to + * capture all eligible input regardless of source (touch or cursor). + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() { + Region region = new Region(); + GEOMETRY.union(region); + final int cornerRadius = LARGE_CORNER_SIZE / 2; + + new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region); + } + + /** + * Validate that with the flag disabled, the corner resize regions are the original smaller + * size. + */ + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() { + Region region = new Region(); + GEOMETRY.union(region); + final int cornerRadius = FINE_CORNER_SIZE / 2; + + new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + public void testCalculateControlType_edgeDragResizeEnabled_edges() { + // The input source (touch or cursor) shouldn't impact the edge resize size. + validateCtrlTypeForEdges(/* isTouch= */ false); + validateCtrlTypeForEdges(/* isTouch= */ true); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + public void testCalculateControlType_edgeDragResizeDisabled_edges() { + // Edge resizing is not supported when the flag is disabled. + validateCtrlTypeForEdges(/* isTouch= */ false); + validateCtrlTypeForEdges(/* isTouch= */ false); + } + + private void validateCtrlTypeForEdges(boolean isTouch) { + assertThat(GEOMETRY.calculateCtrlType(isTouch, LEFT_EDGE_POINT.x, + LEFT_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_LEFT); + assertThat(GEOMETRY.calculateCtrlType(isTouch, TOP_EDGE_POINT.x, + TOP_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_TOP); + assertThat(GEOMETRY.calculateCtrlType(isTouch, RIGHT_EDGE_POINT.x, + RIGHT_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_RIGHT); + assertThat(GEOMETRY.calculateCtrlType(isTouch, BOTTOM_EDGE_POINT.x, + BOTTOM_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_BOTTOM); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + public void testCalculateControlType_edgeDragResizeEnabled_corners() { + final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2); + final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2); + + // When the flag is enabled, points within fine corners should pass regardless of touch or + // not. Points outside fine corners should not pass when using a course input (non-touch). + fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, true); + fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true, true); + fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, true); + fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false, false); + + // When the flag is enabled, points near the large corners should only pass when the point + // is within the corner for large touch inputs. + largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, true); + largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true, + false); + largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, false); + largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false, + false); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + public void testCalculateControlType_edgeDragResizeDisabled_corners() { + final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2); + final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2); + + // When the flag is disabled, points within fine corners should pass only when touch. + fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, true); + fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true, false); + fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, false); + fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false, false); + + // When the flag is disabled, points near the large corners should never pass. + largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, false); + largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true, + false); + largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, false); + largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false, + false); + } + + /** + * Class for creating points for testing the drag resize corners. + * + * <p>Creates points that are both just within the bounds of each corner, and just outside. + */ + private static final class TestPoints { + private final Point mTopLeftPoint; + private final Point mTopLeftPointOutside; + private final Point mTopRightPoint; + private final Point mTopRightPointOutside; + private final Point mBottomLeftPoint; + private final Point mBottomLeftPointOutside; + private final Point mBottomRightPoint; + private final Point mBottomRightPointOutside; + + TestPoints(@NonNull Size taskSize, int cornerRadius) { + // Point just inside corner square is included. + mTopLeftPoint = new Point(-cornerRadius + 1, -cornerRadius + 1); + // Point just outside corner square is excluded. + mTopLeftPointOutside = new Point(mTopLeftPoint.x - 5, mTopLeftPoint.y - 5); + + mTopRightPoint = new Point(taskSize.getWidth() + cornerRadius - 1, -cornerRadius + 1); + mTopRightPointOutside = new Point(mTopRightPoint.x + 5, mTopRightPoint.y - 5); + + mBottomLeftPoint = new Point(-cornerRadius + 1, + taskSize.getHeight() + cornerRadius - 1); + mBottomLeftPointOutside = new Point(mBottomLeftPoint.x - 5, mBottomLeftPoint.y + 5); + + mBottomRightPoint = new Point(taskSize.getWidth() + cornerRadius - 1, + taskSize.getHeight() + cornerRadius - 1); + mBottomRightPointOutside = new Point(mBottomRightPoint.x + 5, mBottomRightPoint.y + 5); + } + + /** + * Validates that all test points are either within or without the given region. + */ + public void validateRegion(@NonNull Region region) { + // Point just inside corner square is included. + assertThat(region.contains(mTopLeftPoint.x, mTopLeftPoint.y)).isTrue(); + // Point just outside corner square is excluded. + assertThat(region.contains(mTopLeftPointOutside.x, mTopLeftPointOutside.y)).isFalse(); + + assertThat(region.contains(mTopRightPoint.x, mTopRightPoint.y)).isTrue(); + assertThat( + region.contains(mTopRightPointOutside.x, mTopRightPointOutside.y)).isFalse(); + + assertThat(region.contains(mBottomLeftPoint.x, mBottomLeftPoint.y)).isTrue(); + assertThat(region.contains(mBottomLeftPointOutside.x, + mBottomLeftPointOutside.y)).isFalse(); + + assertThat(region.contains(mBottomRightPoint.x, mBottomRightPoint.y)).isTrue(); + assertThat(region.contains(mBottomRightPointOutside.x, + mBottomRightPointOutside.y)).isFalse(); + } + + /** + * Validates that all test points within this drag corner size give the correct + * {@code @DragPositioningCallback.CtrlType}. + */ + public void validateCtrlTypeForInnerPoints(@NonNull DragResizeWindowGeometry geometry, + boolean isTouch, boolean expectedWithinGeometry) { + assertThat(geometry.calculateCtrlType(isTouch, mTopLeftPoint.x, + mTopLeftPoint.y)).isEqualTo( + expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED); + assertThat(geometry.calculateCtrlType(isTouch, mTopRightPoint.x, + mTopRightPoint.y)).isEqualTo( + expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED); + assertThat(geometry.calculateCtrlType(isTouch, mBottomLeftPoint.x, + mBottomLeftPoint.y)).isEqualTo( + expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM + : CTRL_TYPE_UNDEFINED); + assertThat(geometry.calculateCtrlType(isTouch, mBottomRightPoint.x, + mBottomRightPoint.y)).isEqualTo( + expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM + : CTRL_TYPE_UNDEFINED); + } + + /** + * Validates that all test points outside this drag corner size give the correct + * {@code @DragPositioningCallback.CtrlType}. + */ + public void validateCtrlTypeForOutsidePoints(@NonNull DragResizeWindowGeometry geometry, + boolean isTouch, boolean expectedWithinGeometry) { + assertThat(geometry.calculateCtrlType(isTouch, mTopLeftPointOutside.x, + mTopLeftPointOutside.y)).isEqualTo( + expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED); + assertThat(geometry.calculateCtrlType(isTouch, mTopRightPointOutside.x, + mTopRightPointOutside.y)).isEqualTo( + expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED); + assertThat(geometry.calculateCtrlType(isTouch, mBottomLeftPointOutside.x, + mBottomLeftPointOutside.y)).isEqualTo( + expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM + : CTRL_TYPE_UNDEFINED); + assertThat(geometry.calculateCtrlType(isTouch, mBottomRightPointOutside.x, + mBottomRightPointOutside.y)).isEqualTo( + expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM + : CTRL_TYPE_UNDEFINED); + } + } } |