summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java294
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java301
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);
+ }
+ }
}