Merge changes Ic161ec76,I0a8c6897 into main

* changes:
  Enable edge resizing for touch input.
  Refactor drag resize touch regions out of the input listener.
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 4ee2c1a..c2c90c8 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -515,8 +515,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 beead6a..43fd32b 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,17 +16,23 @@
 
 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;
 import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
+import android.util.Size;
 import android.view.Choreographer;
 import android.view.SurfaceControl;
 import android.view.View;
@@ -222,7 +228,6 @@
                     mHandler,
                     mChoreographer,
                     mDisplay.getDisplayId(),
-                    0 /* taskCornerRadius */,
                     mDecorationContainerSurface,
                     mDragPositioningCallback,
                     mSurfaceControlBuilderSupplier,
@@ -234,12 +239,10 @@
                 .getScaledTouchSlop();
         mDragDetector.setTouchSlop(touchSlop);
 
-        final int resize_handle = mResult.mRootView.getResources()
-                .getDimensionPixelSize(R.dimen.freeform_resize_handle);
-        final int resize_corner = mResult.mRootView.getResources()
-                .getDimensionPixelSize(R.dimen.freeform_resize_corner);
-        mDragResizeListener.setGeometry(
-                mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
+        final Resources res = mResult.mRootView.getResources();
+        mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
+                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 963b130..2bbe530 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_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;
@@ -42,6 +45,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.util.Log;
+import android.util.Size;
 import android.view.Choreographer;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
@@ -276,7 +280,6 @@
                     mHandler,
                     mChoreographer,
                     mDisplay.getDisplayId(),
-                    mRelayoutParams.mCornerRadius,
                     mDecorationContainerSurface,
                     mDragPositioningCallback,
                     mSurfaceControlBuilderSupplier,
@@ -288,15 +291,13 @@
                 .getScaledTouchSlop();
         mDragDetector.setTouchSlop(touchSlop);
 
-        final int resize_handle = mResult.mRootView.getResources()
-                .getDimensionPixelSize(R.dimen.freeform_resize_handle);
-        final int resize_corner = 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(
-                mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop)
+                new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
+                        new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
+                        getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop)
                 || !mTaskInfo.positionInParent.equals(mPositionInParent)) {
             updateExclusionRegion();
         }
@@ -427,7 +428,7 @@
         return mHandleMenu != null;
     }
 
-    boolean shouldResizeListenerHandleEvent(MotionEvent e, Point offset) {
+    boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
         return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset);
     }
 
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 8ce2d6d..421ffd9 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 @@
  * 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/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 97eb4a4..9624d46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -30,6 +30,7 @@
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -39,6 +40,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Size;
 import android.view.Choreographer;
 import android.view.IWindowSession;
 import android.view.InputChannel;
@@ -55,6 +57,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -66,40 +69,20 @@
 class DragResizeInputListener implements AutoCloseable {
     private static final String TAG = "DragResizeInputListener";
     private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
-    private final Context mContext;
-    private final Handler mHandler;
-    private final Choreographer mChoreographer;
-    private final InputManager mInputManager;
     private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
 
     private final int mDisplayId;
 
     private final IBinder mClientToken;
 
-    private final InputTransferToken mInputTransferToken;
     private final SurfaceControl mDecorationSurface;
     private final InputChannel mInputChannel;
     private final TaskResizeInputEventReceiver mInputEventReceiver;
-    private final DragPositioningCallback mCallback;
 
     private final SurfaceControl mInputSinkSurface;
     private final IBinder mSinkClientToken;
     private final InputChannel mSinkInputChannel;
     private final DisplayController mDisplayController;
-
-    private int mTaskWidth;
-    private int mTaskHeight;
-    private int mResizeHandleThickness;
-    private int mCornerSize;
-    private int mTaskCornerRadius;
-
-    private Rect mLeftTopCornerBounds;
-    private Rect mRightTopCornerBounds;
-    private Rect mLeftBottomCornerBounds;
-    private Rect mRightBottomCornerBounds;
-
-    private int mDragPointerId = -1;
-    private DragDetector mDragDetector;
     private final Region mTouchRegion = new Region();
 
     DragResizeInputListener(
@@ -107,23 +90,17 @@
             Handler handler,
             Choreographer choreographer,
             int displayId,
-            int taskCornerRadius,
             SurfaceControl decorationSurface,
             DragPositioningCallback callback,
             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
             DisplayController displayController) {
-        mInputManager = context.getSystemService(InputManager.class);
-        mContext = context;
-        mHandler = handler;
-        mChoreographer = choreographer;
         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
         mDisplayId = displayId;
-        mTaskCornerRadius = taskCornerRadius;
         mDecorationSurface = decorationSurface;
         mDisplayController = displayController;
         mClientToken = new Binder();
-        mInputTransferToken = new InputTransferToken();
+        final InputTransferToken inputTransferToken = new InputTransferToken();
         mInputChannel = new InputChannel();
         try {
             mWindowSession.grantInputChannel(
@@ -136,18 +113,19 @@
                     INPUT_FEATURE_SPY,
                     TYPE_APPLICATION,
                     null /* windowToken */,
-                    mInputTransferToken,
+                    inputTransferToken,
                     TAG + " of " + decorationSurface.toString(),
                     mInputChannel);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
 
-        mInputEventReceiver = new TaskResizeInputEventReceiver(
-                mInputChannel, mHandler, mChoreographer);
-        mCallback = callback;
-        mDragDetector = new DragDetector(mInputEventReceiver);
-        mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
+        mInputEventReceiver = new TaskResizeInputEventReceiver(context, mInputChannel, callback,
+                handler, choreographer, () -> {
+            final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
+            return new Size(layout.width(), layout.height());
+        }, this::updateSinkInputChannel);
+        mInputEventReceiver.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
 
         mInputSinkSurface = surfaceControlBuilderSupplier.get()
                 .setName("TaskInputSink of " + decorationSurface)
@@ -171,7 +149,7 @@
                     INPUT_FEATURE_NO_INPUT_CHANNEL,
                     TYPE_INPUT_CONSUMER,
                     null /* windowToken */,
-                    mInputTransferToken,
+                    inputTransferToken,
                     "TaskInputSink of " + decorationSurface,
                     mSinkInputChannel);
         } catch (RemoteException e) {
@@ -182,86 +160,26 @@
     /**
      * Updates the geometry (the touch region) of this drag resize handler.
      *
-     * @param taskWidth The width of the task.
-     * @param taskHeight The height of the task.
-     * @param resizeHandleThickness The thickness of the resize handle in pixels.
-     * @param cornerSize The size of the resize handle centered in each corner.
-     * @param touchSlop The distance in pixels user has to drag with touch for it to register as
-     *                  a resize action.
+     * @param incomingGeometry The geometry update to apply for this task's drag resize regions.
+     * @param touchSlop        The distance in pixels user has to drag with touch for it to register
+     *                         as a resize action.
      * @return whether the geometry has changed or not
      */
-    boolean setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
-            int touchSlop) {
-        if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
-                && mResizeHandleThickness == resizeHandleThickness
-                && mCornerSize == cornerSize) {
+    boolean setGeometry(@NonNull DragResizeWindowGeometry incomingGeometry, int touchSlop) {
+        DragResizeWindowGeometry geometry = mInputEventReceiver.getGeometry();
+        if (incomingGeometry.equals(geometry)) {
+            // Geometry hasn't changed size so skip all updates.
             return false;
+        } else {
+            geometry = incomingGeometry;
         }
-
-        mTaskWidth = taskWidth;
-        mTaskHeight = taskHeight;
-        mResizeHandleThickness = resizeHandleThickness;
-        mCornerSize = cornerSize;
-        mDragDetector.setTouchSlop(touchSlop);
+        mInputEventReceiver.setTouchSlop(touchSlop);
 
         mTouchRegion.setEmpty();
-        final Rect topInputBounds = new Rect(
-                -mResizeHandleThickness,
-                -mResizeHandleThickness,
-                mTaskWidth + mResizeHandleThickness,
-                0);
-        mTouchRegion.union(topInputBounds);
-
-        final Rect leftInputBounds = new Rect(
-                -mResizeHandleThickness,
-                0,
-                0,
-                mTaskHeight);
-        mTouchRegion.union(leftInputBounds);
-
-        final Rect rightInputBounds = new Rect(
-                mTaskWidth,
-                0,
-                mTaskWidth + mResizeHandleThickness,
-                mTaskHeight);
-        mTouchRegion.union(rightInputBounds);
-
-        final Rect bottomInputBounds = new Rect(
-                -mResizeHandleThickness,
-                mTaskHeight,
-                mTaskWidth + mResizeHandleThickness,
-                mTaskHeight + mResizeHandleThickness);
-        mTouchRegion.union(bottomInputBounds);
-
-        // Set up touch areas in each corner.
-        int cornerRadius = mCornerSize / 2;
-        mLeftTopCornerBounds = new Rect(
-                -cornerRadius,
-                -cornerRadius,
-                cornerRadius,
-                cornerRadius);
-        mTouchRegion.union(mLeftTopCornerBounds);
-
-        mRightTopCornerBounds = new Rect(
-                mTaskWidth - cornerRadius,
-                -cornerRadius,
-                mTaskWidth + cornerRadius,
-                cornerRadius);
-        mTouchRegion.union(mRightTopCornerBounds);
-
-        mLeftBottomCornerBounds = new Rect(
-                -cornerRadius,
-                mTaskHeight - cornerRadius,
-                cornerRadius,
-                mTaskHeight + cornerRadius);
-        mTouchRegion.union(mLeftBottomCornerBounds);
-
-        mRightBottomCornerBounds = new Rect(
-                mTaskWidth - cornerRadius,
-                mTaskHeight - cornerRadius,
-                mTaskWidth + cornerRadius,
-                mTaskHeight + cornerRadius);
-        mTouchRegion.union(mRightBottomCornerBounds);
+        // Apply the geometry to the touch region.
+        geometry.union(mTouchRegion);
+        mInputEventReceiver.setGeometry(geometry);
+        mInputEventReceiver.setTouchRegion(mTouchRegion);
 
         try {
             mWindowSession.updateInputChannel(
@@ -276,8 +194,9 @@
             e.rethrowFromSystemServer();
         }
 
+        final Size taskSize = geometry.getTaskSize();
         mSurfaceControlTransactionSupplier.get()
-                .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight)
+                .setWindowCrop(mInputSinkSurface, taskSize.getWidth(), taskSize.getHeight())
                 .apply();
         // The touch region of the TaskInputSink should be the touch region of this
         // DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent
@@ -290,21 +209,16 @@
         // issue. However, were there touchscreen-only a region out of the task bounds, mouse
         // gestures will become no-op in that region, even though the mouse gestures may appear to
         // be performed on the input window behind the resize handle.
-        mTouchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
+        mTouchRegion.op(0, 0, taskSize.getWidth(), taskSize.getHeight(), Region.Op.DIFFERENCE);
         updateSinkInputChannel(mTouchRegion);
         return true;
     }
 
     /**
-     * Generate a Region that encapsulates all 4 corner handles
+     * Generate a Region that encapsulates all 4 corner handles and window edges.
      */
-    Region getCornersRegion() {
-        Region region = new Region();
-        region.union(mLeftTopCornerBounds);
-        region.union(mLeftBottomCornerBounds);
-        region.union(mRightTopCornerBounds);
-        region.union(mRightBottomCornerBounds);
-        return region;
+    @NonNull Region getCornersRegion() {
+        return mInputEventReceiver.getCornersRegion();
     }
 
     private void updateSinkInputChannel(Region region) {
@@ -322,7 +236,7 @@
         }
     }
 
-    boolean shouldHandleEvent(MotionEvent e, Point offset) {
+    boolean shouldHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
         return mInputEventReceiver.shouldHandleEvent(e, offset);
     }
 
@@ -351,19 +265,37 @@
                 .apply();
     }
 
-    private class TaskResizeInputEventReceiver extends InputEventReceiver
-            implements DragDetector.MotionEventHandler {
-        private final Choreographer mChoreographer;
-        private final Runnable mConsumeBatchEventRunnable;
+    private static class TaskResizeInputEventReceiver extends InputEventReceiver implements
+            DragDetector.MotionEventHandler {
+        @NonNull private final Context mContext;
+        private final InputManager mInputManager;
+        @NonNull private final InputChannel mInputChannel;
+        @NonNull private final DragPositioningCallback mCallback;
+        @NonNull private final Choreographer mChoreographer;
+        @NonNull private final Runnable mConsumeBatchEventRunnable;
+        @NonNull private final DragDetector mDragDetector;
+        @NonNull private final Supplier<Size> mDisplayLayoutSizeSupplier;
+        @NonNull private final Consumer<Region> mTouchRegionConsumer;
+        private final Rect mTmpRect = new Rect();
         private boolean mConsumeBatchEventScheduled;
+        private DragResizeWindowGeometry mDragResizeWindowGeometry;
+        private Region mTouchRegion;
         private boolean mShouldHandleEvents;
         private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
         private Rect mDragStartTaskBounds;
-        private final Rect mTmpRect = new Rect();
+        private int mDragPointerId = -1;
 
-        private TaskResizeInputEventReceiver(
-                InputChannel inputChannel, Handler handler, Choreographer choreographer) {
+        private TaskResizeInputEventReceiver(@NonNull Context context,
+                @NonNull InputChannel inputChannel,
+                @NonNull DragPositioningCallback callback, @NonNull Handler handler,
+                @NonNull Choreographer choreographer,
+                @NonNull Supplier<Size> displayLayoutSizeSupplier,
+                @NonNull Consumer<Region> touchRegionConsumer) {
             super(inputChannel, handler.getLooper());
+            mContext = context;
+            mInputManager = context.getSystemService(InputManager.class);
+            mInputChannel = inputChannel;
+            mCallback = callback;
             mChoreographer = choreographer;
 
             mConsumeBatchEventRunnable = () -> {
@@ -376,6 +308,48 @@
                     scheduleConsumeBatchEvent();
                 }
             };
+
+            mDragDetector = new DragDetector(this);
+            mDisplayLayoutSizeSupplier = displayLayoutSizeSupplier;
+            mTouchRegionConsumer = touchRegionConsumer;
+        }
+
+        /**
+         * Returns the geometry of the areas to drag resize.
+         */
+        DragResizeWindowGeometry getGeometry() {
+            return mDragResizeWindowGeometry;
+        }
+
+        /**
+         * Updates the geometry of the areas to drag resize.
+         */
+        void setGeometry(@NonNull DragResizeWindowGeometry dragResizeWindowGeometry) {
+            mDragResizeWindowGeometry = dragResizeWindowGeometry;
+        }
+
+        /**
+         * Sets how much slop to allow for touches.
+         */
+        void setTouchSlop(int touchSlop) {
+            mDragDetector.setTouchSlop(touchSlop);
+        }
+
+        /**
+         * Updates the region accepting input for drag resizing the task.
+         */
+        void setTouchRegion(@NonNull Region touchRegion) {
+            mTouchRegion = touchRegion;
+        }
+
+        /**
+         * Returns the union of all regions that can be touched for drag resizing; the corners and
+         * window edges.
+         */
+        @NonNull Region getCornersRegion() {
+            Region region = new Region();
+            mDragResizeWindowGeometry.union(region);
+            return region;
         }
 
         @Override
@@ -416,14 +390,15 @@
             boolean isTouch = isTouchEvent(e);
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
-                    mShouldHandleEvents = shouldHandleEvent(e, isTouch, new Point() /* offset */);
+                    mShouldHandleEvents = mDragResizeWindowGeometry.shouldHandleEvent(e, isTouch,
+                            new Point() /* offset */);
                     if (mShouldHandleEvents) {
                         mDragPointerId = e.getPointerId(0);
                         float x = e.getX(0);
                         float y = e.getY(0);
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
-                        int ctrlType = calculateCtrlType(isTouch, x, y);
+                        int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(isTouch, x, y);
                         mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
                                 rawX, rawY);
                         // Increase the input sink region to cover the whole screen; this is to
@@ -455,7 +430,7 @@
                         // If taskBounds has changed, setGeometry will be called and update the
                         // sink region. Otherwise, we should revert it here.
                         if (taskBounds.equals(mDragStartTaskBounds)) {
-                            updateSinkInputChannel(mTouchRegion);
+                            mTouchRegionConsumer.accept(mTouchRegion);
                         }
                     }
                     mShouldHandleEvents = false;
@@ -480,125 +455,20 @@
 
         private void updateInputSinkRegionForDrag(Rect taskBounds) {
             mTmpRect.set(taskBounds);
-            final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
-            final Region dragTouchRegion = new Region(-taskBounds.left,
-                    -taskBounds.top,
-                    -taskBounds.left + layout.width(),
-                    -taskBounds.top + layout.height());
+            final Size displayLayoutSize = mDisplayLayoutSizeSupplier.get();
+            final Region dragTouchRegion = new Region(-taskBounds.left, -taskBounds.top,
+                    -taskBounds.left + displayLayoutSize.getWidth(),
+                    -taskBounds.top + displayLayoutSize.getHeight());
             // Remove the localized task bounds from the touch region.
             mTmpRect.offsetTo(0, 0);
             dragTouchRegion.op(mTmpRect, Region.Op.DIFFERENCE);
-            updateSinkInputChannel(dragTouchRegion);
-        }
-
-        private boolean isInCornerBounds(float xf, float yf) {
-            return calculateCornersCtrlType(xf, yf) != 0;
-        }
-
-        private boolean isInResizeHandleBounds(float x, float y) {
-            return calculateResizeHandlesCtrlType(x, y) != 0;
-        }
-
-        @DragPositioningCallback.CtrlType
-        private int calculateCtrlType(boolean isTouch, float x, float y) {
-            if (isTouch) {
-                return calculateCornersCtrlType(x, y);
-            }
-            return calculateResizeHandlesCtrlType(x, y);
-        }
-
-        @DragPositioningCallback.CtrlType
-        private int calculateResizeHandlesCtrlType(float x, float y) {
-            int ctrlType = 0;
-            // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
-            // sides will use the bounds specified in setGeometry and not go into task bounds.
-            if (x < mTaskCornerRadius) {
-                ctrlType |= CTRL_TYPE_LEFT;
-            }
-            if (x > mTaskWidth - mTaskCornerRadius) {
-                ctrlType |= CTRL_TYPE_RIGHT;
-            }
-            if (y < mTaskCornerRadius) {
-                ctrlType |= CTRL_TYPE_TOP;
-            }
-            if (y > mTaskHeight - mTaskCornerRadius) {
-                ctrlType |= CTRL_TYPE_BOTTOM;
-            }
-            // Check distances from the center if it's in one of four corners.
-            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 >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0;
-        }
-
-        // 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.
-        @DragPositioningCallback.CtrlType
-        private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType,
-                float x, float y) {
-            int centerX;
-            int centerY;
-
-            // Determine center of rounded corner circle; this is simply the corner if radius is 0.
-            switch (ctrlType) {
-                case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: {
-                    centerX = mTaskCornerRadius;
-                    centerY = mTaskCornerRadius;
-                    break;
-                }
-                case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: {
-                    centerX = mTaskCornerRadius;
-                    centerY = mTaskHeight - mTaskCornerRadius;
-                    break;
-                }
-                case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: {
-                    centerX = mTaskWidth - mTaskCornerRadius;
-                    centerY = mTaskCornerRadius;
-                    break;
-                }
-                case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: {
-                    centerX = mTaskWidth - mTaskCornerRadius;
-                    centerY = mTaskHeight - mTaskCornerRadius;
-                    break;
-                }
-                default: {
-                    throw new IllegalArgumentException("ctrlType should be complex, but it's 0x"
-                            + Integer.toHexString(ctrlType));
-                }
-            }
-            double distanceFromCenter = Math.hypot(x - centerX, y - centerY);
-
-            if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
-                    && distanceFromCenter >= mTaskCornerRadius) {
-                return ctrlType;
-            }
-            return 0;
-        }
-
-        @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;
-            }
-            return 0;
+            mTouchRegionConsumer.accept(dragTouchRegion);
         }
 
         private void updateCursorType(int displayId, int deviceId, int pointerId, float x,
                 float y) {
-            @DragPositioningCallback.CtrlType int ctrlType = calculateResizeHandlesCtrlType(x, y);
+            @DragPositioningCallback.CtrlType int ctrlType =
+                    mDragResizeWindowGeometry.calculateCtrlType(/* isTouch= */ false, x, y);
 
             int cursorType = PointerIcon.TYPE_DEFAULT;
             switch (ctrlType) {
@@ -640,19 +510,7 @@
         }
 
         private boolean shouldHandleEvent(MotionEvent e, Point offset) {
-            return shouldHandleEvent(e, isTouchEvent(e), offset);
-        }
-
-        private boolean shouldHandleEvent(MotionEvent e, boolean isTouch, 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);
-            } else {
-                result = isInResizeHandleBounds(x, y);
-            }
-            return result;
+            return mDragResizeWindowGeometry.shouldHandleEvent(e, offset);
         }
 
         private boolean isTouchEvent(MotionEvent e) {
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
new file mode 100644
index 0000000..eafb569
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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 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 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;
+    private final @NonNull Rect mRightEdgeBounds;
+    private final @NonNull Rect mBottomEdgeBounds;
+
+    DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
+            int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
+        mTaskCornerRadius = taskCornerRadius;
+        mTaskSize = taskSize;
+        mResizeHandleThickness = resizeHandleThickness;
+
+        mLargeTaskCorners = new TaskCorners(mTaskSize, largeCornerSize);
+        mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize);
+
+        // Save touch areas for each edge.
+        mTopEdgeBounds = new Rect(
+                -mResizeHandleThickness,
+                -mResizeHandleThickness,
+                mTaskSize.getWidth() + mResizeHandleThickness,
+                0);
+        mLeftEdgeBounds = new Rect(
+                -mResizeHandleThickness,
+                0,
+                0,
+                mTaskSize.getHeight());
+        mRightEdgeBounds = new Rect(
+                mTaskSize.getWidth(),
+                0,
+                mTaskSize.getWidth() + mResizeHandleThickness,
+                mTaskSize.getHeight());
+        mBottomEdgeBounds = new Rect(
+                -mResizeHandleThickness,
+                mTaskSize.getHeight(),
+                mTaskSize.getWidth() + mResizeHandleThickness,
+                mTaskSize.getHeight() + mResizeHandleThickness);
+    }
+
+    /**
+     * 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() {
+        // Safe to return directly since size is immutable.
+        return mTaskSize;
+    }
+
+    /**
+     * Returns the union of all regions that can be touched for drag resizing; the corners window
+     * and window edges.
+     */
+    void union(@NonNull Region region) {
+        // Apply the edge resize regions.
+        region.union(mTopEdgeBounds);
+        region.union(mLeftEdgeBounds);
+        region.union(mRightEdgeBounds);
+        region.union(mBottomEdgeBounds);
+
+        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);
+        }
+    }
+
+    /**
+     * Returns if this MotionEvent should be handled, based on its source and position.
+     */
+    boolean shouldHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
+        return shouldHandleEvent(e, isTouchEvent(e), offset);
+    }
+
+    /**
+     * Returns if this MotionEvent should be handled, based on its source and position.
+     */
+    boolean shouldHandleEvent(@NonNull MotionEvent e, boolean isTouch, @NonNull Point offset) {
+        final float x = e.getX(0) + offset.x;
+        final float y = e.getY(0) + offset.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 {
+            // Legacy uses only fine corners for touch, and edges only for non-touch input.
+            return isTouch
+                    ? isInCornerBounds(mFineTaskCorners, x, y)
+                    : isInEdgeResizeBounds(x, y);
+        }
+    }
+
+    private boolean isTouchEvent(@NonNull MotionEvent e) {
+        return (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
+    }
+
+    private boolean isInCornerBounds(TaskCorners corners, float xf, float yf) {
+        return corners.calculateCornersCtrlType(xf, yf) != 0;
+    }
+
+    private boolean isInEdgeResizeBounds(float x, float y) {
+        return calculateEdgeResizeCtrlType(x, y) != 0;
+    }
+
+    /**
+     * Returns the control type for the drag-resize, based on the touch regions and this
+     * MotionEvent's coordinates.
+     */
+    @DragPositioningCallback.CtrlType
+    int calculateCtrlType(boolean isTouch, float x, float y) {
+        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);
+        }
+    }
+
+    @DragPositioningCallback.CtrlType
+    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 in setGeometry and not go into task bounds.
+        if (x < mTaskCornerRadius) {
+            ctrlType |= CTRL_TYPE_LEFT;
+        }
+        if (x > mTaskSize.getWidth() - mTaskCornerRadius) {
+            ctrlType |= CTRL_TYPE_RIGHT;
+        }
+        if (y < mTaskCornerRadius) {
+            ctrlType |= CTRL_TYPE_TOP;
+        }
+        if (y > mTaskSize.getHeight() - mTaskCornerRadius) {
+            ctrlType |= CTRL_TYPE_BOTTOM;
+        }
+        // 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 : CTRL_TYPE_UNDEFINED;
+    }
+
+    /**
+     * 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,
+            float y) {
+        final Point cornerRadiusCenter = calculateCenterForCornerRadius(ctrlType);
+        double distanceFromCenter = Math.hypot(x - cornerRadiusCenter.x, y - cornerRadiusCenter.y);
+
+        if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
+                && distanceFromCenter >= mTaskCornerRadius) {
+            return ctrlType;
+        }
+        return CTRL_TYPE_UNDEFINED;
+    }
+
+    /**
+     * Returns center of rounded corner circle; this is simply the corner if radius is 0.
+     */
+    private Point calculateCenterForCornerRadius(@DragPositioningCallback.CtrlType int ctrlType) {
+        int centerX;
+        int centerY;
+
+        switch (ctrlType) {
+            case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: {
+                centerX = mTaskCornerRadius;
+                centerY = mTaskCornerRadius;
+                break;
+            }
+            case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: {
+                centerX = mTaskCornerRadius;
+                centerY = mTaskSize.getHeight() - mTaskCornerRadius;
+                break;
+            }
+            case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: {
+                centerX = mTaskSize.getWidth() - mTaskCornerRadius;
+                centerY = mTaskCornerRadius;
+                break;
+            }
+            case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: {
+                centerX = mTaskSize.getWidth() - mTaskCornerRadius;
+                centerY = mTaskSize.getHeight() - mTaskCornerRadius;
+                break;
+            }
+            default: {
+                throw new IllegalArgumentException(
+                        "ctrlType should be complex, but it's 0x" + Integer.toHexString(ctrlType));
+            }
+        }
+        return new Point(centerX, centerY);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) return false;
+        if (this == obj) return true;
+        if (!(obj instanceof DragResizeWindowGeometry other)) return false;
+
+        return this.mTaskCornerRadius == other.mTaskCornerRadius
+                && this.mTaskSize.equals(other.mTaskSize)
+                && this.mResizeHandleThickness == other.mResizeHandleThickness
+                && 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)
+                && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mTaskCornerRadius,
+                mTaskSize,
+                mResizeHandleThickness,
+                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 32c0703..13f95cc 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -39,7 +39,7 @@
     static_libs: [
         "WindowManager-Shell",
         "junit",
-        "flag-junit-base",
+        "flag-junit",
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.ext.junit",
@@ -55,6 +55,9 @@
         "platform-test-annotations",
         "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
new file mode 100644
index 0000000..82e5a1c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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 DragResizeWindowGeometry}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DragResizeWindowGeometryTests
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DragResizeWindowGeometryTests {
+    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
+     * group is distinct from the next.
+     */
+    @Test
+    public void testEqualsAndHash() {
+        new EqualsTester()
+                .addEqualityGroup(
+                        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);
+        }
+    }
+}