Merge "Start using InputMethodSettingsRepository (2nd try)" into main
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 9696dbc..9c14946 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -20,6 +20,7 @@
 import android.annotation.StringDef;
 import android.annotation.SystemApi;
 import android.app.Notification;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -146,8 +147,13 @@
 
     /**
      * Data type: boolean, when true it suggests that the content text of this notification is
-     * sensitive. A notification listener can use this information to redact notifications on locked
-     * devices.
+     * sensitive. The system uses this information to improve privacy around the notification
+     * content. In {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, sensitive notification content is
+     * redacted from updates to most {@link NotificationListenerService
+     * NotificationListenerServices}. Also if an app posts a sensitive notification while
+     * {@link android.media.projection.MediaProjection screen-sharing} is active, that app's windows
+     * are blocked from screen-sharing and a {@link android.widget.Toast Toast} is shown to inform
+     * the user about this.
      */
     public static final String KEY_SENSITIVE_CONTENT = "key_sensitive_content";
 
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 188ad8f..56edfe72 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -103,9 +103,9 @@
             long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
     private static native void nativeDestroy(long nativeObject);
 
-    // 5MB is a wild guess for what the average surface should be. On most new phones, a full-screen
-    // surface is about 9MB... but not all surfaces are screen size. This should be a nice balance.
-    private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000_000;
+    // 5KB is a balanced guess, since these are still pretty heavyweight objects, but if we make
+    // this too big, it can overwhelm the GC.
+    private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000;
 
     public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
             new Parcelable.Creator<Surface>() {
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);
+        }
+    }
+}
diff --git a/packages/SystemUI/res/layout/auth_biometric_icon.xml b/packages/SystemUI/res/layout/auth_biometric_icon.xml
deleted file mode 100644
index b2df63d..0000000
--- a/packages/SystemUI/res/layout/auth_biometric_icon.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 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.
-  -->
-
-
-<com.airbnb.lottie.LottieAnimationView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/biometric_icon"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:contentDescription="@null"
-    android:scaleType="fitXY"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/shelf_action_chip.xml b/packages/SystemUI/res/layout/shelf_action_chip.xml
index 709c80d..c7606e4 100644
--- a/packages/SystemUI/res/layout/shelf_action_chip.xml
+++ b/packages/SystemUI/res/layout/shelf_action_chip.xml
@@ -14,33 +14,27 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.screenshot.OverlayActionChip
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:id="@+id/overlay_action_chip"
     android:theme="@style/FloatingOverlay"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:layout_gravity="center"
+    android:orientation="horizontal"
+    android:paddingVertical="@dimen/overlay_action_chip_padding_vertical"
     android:gravity="center"
-    android:alpha="0.0">
-    <LinearLayout
+    android:background="@drawable/shelf_action_chip_background"
+    >
+    <ImageView
+        android:id="@+id/overlay_action_chip_icon"
+        android:tint="?androidprv:attr/materialColorOnSecondary"
+        android:layout_width="@dimen/overlay_action_chip_icon_size"
+        android:layout_height="@dimen/overlay_action_chip_icon_size"/>
+    <TextView
+        android:id="@+id/overlay_action_chip_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:paddingVertical="@dimen/overlay_action_chip_padding_vertical"
-        android:background="@drawable/shelf_action_chip_background"
-        android:gravity="center">
-        <ImageView
-            android:id="@+id/overlay_action_chip_icon"
-            android:tint="?androidprv:attr/materialColorOnSecondary"
-            android:layout_width="@dimen/overlay_action_chip_icon_size"
-            android:layout_height="@dimen/overlay_action_chip_icon_size"/>
-        <TextView
-            android:id="@+id/overlay_action_chip_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-            android:textSize="@dimen/overlay_action_chip_text_size"
-            android:textColor="?androidprv:attr/materialColorOnSecondary"/>
-    </LinearLayout>
-</com.android.systemui.screenshot.OverlayActionChip>
+        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+        android:textSize="@dimen/overlay_action_chip_text_size"
+        android:textColor="?androidprv:attr/materialColorOnSecondary"/>
+</LinearLayout>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 4987724..8c51a4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -225,7 +225,7 @@
     protected static final int BIOMETRIC_STATE_STOPPED = 0;
 
     /** Biometric authentication state: Listening. */
-    protected static final int BIOMETRIC_STATE_RUNNING = 1;
+    private static final int BIOMETRIC_STATE_RUNNING = 1;
 
     /**
      * Biometric authentication: Cancelling and waiting for the relevant biometric service to
@@ -1145,6 +1145,7 @@
         if (getUserCanSkipBouncer(userId)) {
             mTrustManager.unlockedByBiometricForUser(userId, FACE);
         }
+        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         mLogger.d("onFaceAuthenticated");
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1155,12 +1156,6 @@
             }
         }
 
-        // Intentionally update the fingerprint running state after sending the
-        // onBiometricAuthenticated callback to listeners. Updating the fingerprint listening state
-        // can update the state of the device which listeners to the callback may rely on.
-        // For example, the alternate bouncer visibility state or udfps finger down state.
-        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
-
         // Only authenticate face once when assistant is visible
         mAssistantVisible = false;
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 66b7d7a..d9d3715 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -26,6 +26,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieOnCompositionLoadedListener
 import com.android.settingslib.widget.LottieColorUtils
 import com.android.systemui.Flags.constraintBp
 import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
@@ -77,6 +78,8 @@
                     }
 
                 launch {
+                    var lottieOnCompositionLoadedListener: LottieOnCompositionLoadedListener? = null
+
                     combine(viewModel.activeAuthType, viewModel.iconSize, ::Pair).collect {
                         (activeAuthType, iconSize) ->
                         // Every time after bp shows, [isIconViewLoaded] is set to false in
@@ -94,10 +97,18 @@
                                  * TODO(b/288175072): May be able to remove this once constraint
                                  *   layout is implemented
                                  */
-                                iconView.removeAllLottieOnCompositionLoadedListener()
-                                iconView.addLottieOnCompositionLoadedListener {
-                                    promptViewModel.setIsIconViewLoaded(true)
+                                if (lottieOnCompositionLoadedListener != null) {
+                                    iconView.removeLottieOnCompositionLoadedListener(
+                                        lottieOnCompositionLoadedListener!!
+                                    )
                                 }
+                                lottieOnCompositionLoadedListener =
+                                    LottieOnCompositionLoadedListener {
+                                        promptViewModel.setIsIconViewLoaded(true)
+                                    }
+                                iconView.addLottieOnCompositionLoadedListener(
+                                    lottieOnCompositionLoadedListener!!
+                                )
                             }
                             AuthType.Face -> {
                                 /**
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 5af0c1f..fde45d3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -52,7 +52,6 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -158,6 +157,7 @@
 import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 import org.mockito.internal.util.reflection.FieldSetter;
@@ -809,31 +809,6 @@
     }
 
     @Test
-    public void whenFaceAuthenticated_biometricAuthenticatedCallback_beforeUpdatingFpState() {
-        // GIVEN listening for UDFPS fingerprint
-        when(mAuthController.isUdfpsSupported()).thenReturn(true);
-        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
-        mTestableLooper.processAllMessages();
-        keyguardIsVisible();
-        final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
-        mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel;
-
-        // WHEN face is authenticated
-        when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true);
-        when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(true);
-        when(mFaceAuthInteractor.isLockedOut()).thenReturn(false);
-        mKeyguardUpdateMonitor.onFaceAuthenticated(0, true);
-        mTestableLooper.processAllMessages();
-
-        // THEN verify keyguardUpdateMonitorCallback receives an onAuthenticated callback
-        // before cancelling the fingerprint request
-        InOrder inOrder = inOrder(mTestCallback, fpCancel);
-        inOrder.verify(mTestCallback).onBiometricAuthenticated(
-                eq(0), eq(BiometricSourceType.FACE), eq(true));
-        inOrder.verify(fpCancel).cancel();
-    }
-
-    @Test
     public void whenDetectFingerprint_biometricDetectCallback() {
         ArgumentCaptor<FingerprintManager.FingerprintDetectionCallback> fpDetectCallbackCaptor =
                 ArgumentCaptor.forClass(FingerprintManager.FingerprintDetectionCallback.class);
@@ -2158,7 +2133,7 @@
                 null /* trustGrantedMessages */);
 
         // THEN onTrustChanged is called FIRST
-        final InOrder inOrder = inOrder(callback);
+        final InOrder inOrder = Mockito.inOrder(callback);
         inOrder.verify(callback).onTrustChanged(eq(mSelectedUserInteractor.getSelectedUserId()));
 
         // AND THEN onTrustGrantedForCurrentUser callback called
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index b1b2cc9..f53a1b0 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -49,7 +49,6 @@
     private static final long ENERGY_UNSPECIFIED = -1;
     private static final int DEFAULT_CPU_POWER_BRACKETS = 3;
     private static final int DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER = 2;
-    private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
 
     interface Injector {
         Handler getHandler();
@@ -76,7 +75,6 @@
     private CpuScalingPolicies mCpuScalingPolicies;
     private PowerProfile mPowerProfile;
     private KernelCpuStatsReader mKernelCpuStatsReader;
-    private PowerStatsUidResolver mUidResolver;
     private ConsumedEnergyRetriever mConsumedEnergyRetriever;
     private IntSupplier mVoltageSupplier;
     private int mDefaultCpuPowerBrackets;
@@ -97,7 +95,8 @@
     private long[] mLastConsumedEnergyUws;
 
     public CpuPowerStatsCollector(Injector injector, long throttlePeriodMs) {
-        super(injector.getHandler(), throttlePeriodMs, injector.getClock());
+        super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
+                injector.getClock());
         mInjector = injector;
     }
 
@@ -113,7 +112,6 @@
         mCpuScalingPolicies = mInjector.getCpuScalingPolicies();
         mPowerProfile = mInjector.getPowerProfile();
         mKernelCpuStatsReader = mInjector.getKernelCpuStatsReader();
-        mUidResolver = mInjector.getUidResolver();
         mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
         mVoltageSupplier = mInjector.getVoltageSupplier();
         mDefaultCpuPowerBrackets = mInjector.getDefaultCpuPowerBrackets();
@@ -421,7 +419,8 @@
 
         boolean nonzero = false;
         for (int bracket = powerBracketCount - 1; bracket >= 0; bracket--) {
-            long delta = timeByPowerBracket[bracket] - uidStats.timeByPowerBracket[bracket];
+            long delta = Math.max(0,
+                    timeByPowerBracket[bracket] - uidStats.timeByPowerBracket[bracket]);
             if (delta != 0) {
                 nonzero = true;
             }
@@ -447,6 +446,12 @@
         }
     }
 
+    @Override
+    protected void onUidRemoved(int uid) {
+        super.onUidRemoved(uid);
+        mUidStats.remove(uid);
+    }
+
     /**
      * Native class that retrieves CPU stats from the kernel.
      */
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
index 8c154e4..7bc6817 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
@@ -89,7 +89,6 @@
 
     private PowerStats mPowerStats;
     private long[] mDeviceStats;
-    private PowerStatsUidResolver mPowerStatsUidResolver;
     private volatile TelephonyManager mTelephonyManager;
     private LongSupplier mCallDurationSupplier;
     private LongSupplier mScanDurationSupplier;
@@ -106,7 +105,8 @@
     private long mLastScanDuration;
 
     public MobileRadioPowerStatsCollector(Injector injector, long throttlePeriodMs) {
-        super(injector.getHandler(), throttlePeriodMs, injector.getClock());
+        super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
+                injector.getClock());
         mInjector = injector;
     }
 
@@ -130,7 +130,6 @@
             return false;
         }
 
-        mPowerStatsUidResolver = mInjector.getUidResolver();
         mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
         mVoltageSupplier = mInjector.getVoltageSupplier();
 
@@ -310,7 +309,7 @@
                 continue;
             }
 
-            int uid = mPowerStatsUidResolver.mapUid(uidDelta.getUid());
+            int uid = mUidResolver.mapUid(uidDelta.getUid());
             long[] stats = mPowerStats.uidStats.get(uid);
             if (stats == null) {
                 stats = new long[mLayout.getUidStatsArrayLength()];
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index 5dd11db..b82c021 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -53,6 +53,7 @@
     private static final int MILLIVOLTS_PER_VOLT = 1000;
     private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
     private final Handler mHandler;
+    protected final PowerStatsUidResolver mUidResolver;
     protected final Clock mClock;
     private final long mThrottlePeriodMs;
     private final Runnable mCollectAndDeliverStats = this::collectAndDeliverStats;
@@ -63,9 +64,25 @@
     @SuppressWarnings("unchecked")
     private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList();
 
-    public PowerStatsCollector(Handler handler, long throttlePeriodMs, Clock clock) {
+    public PowerStatsCollector(Handler handler, long throttlePeriodMs,
+            PowerStatsUidResolver uidResolver, Clock clock) {
         mHandler = handler;
         mThrottlePeriodMs = throttlePeriodMs;
+        mUidResolver = uidResolver;
+        mUidResolver.addListener(new PowerStatsUidResolver.Listener() {
+            @Override
+            public void onIsolatedUidAdded(int isolatedUid, int parentUid) {
+            }
+
+            @Override
+            public void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) {
+            }
+
+            @Override
+            public void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) {
+                mHandler.post(()->onUidRemoved(isolatedUid));
+            }
+        });
         mClock = clock;
     }
 
@@ -203,6 +220,9 @@
         done.block();
     }
 
+    protected void onUidRemoved(int uid) {
+    }
+
     /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
     protected static long uJtoUc(long deltaEnergyUj, int avgVoltageMv) {
         // To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index ed9fa65..c5d3333 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -16,8 +16,10 @@
 
 package com.android.server.vcn.routeselection;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -38,6 +40,10 @@
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.server.vcn.VcnContext;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.util.BitSet;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
@@ -56,8 +62,32 @@
 public class IpSecPacketLossDetector extends NetworkMetricMonitor {
     private static final String TAG = IpSecPacketLossDetector.class.getSimpleName();
 
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PACKET_LOSS_UNAVALAIBLE = -1;
+    private static final int PACKET_LOSS_PERCENT_UNAVAILABLE = -1;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"PACKET_LOSS_"},
+            value = {
+                PACKET_LOSS_RATE_VALID,
+                PACKET_LOSS_RATE_INVALID,
+            })
+    @Target({ElementType.TYPE_USE})
+    private @interface PacketLossResultType {}
+
+    /** Indicates a valid packet loss rate is available */
+    private static final int PACKET_LOSS_RATE_VALID = 0;
+
+    /**
+     * Indicates that the detector cannot get a valid packet loss rate due to one of the following
+     * reasons:
+     *
+     * <ul>
+     *   <li>The replay window did not proceed and thus all packets might have been delivered out of
+     *       order
+     *   <li>There are unexpected errors
+     * </ul>
+     */
+    private static final int PACKET_LOSS_RATE_INVALID = 1;
 
     // For VoIP, losses between 5% and 10% of the total packet stream will affect the quality
     // significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and
@@ -307,24 +337,24 @@
             return;
         }
 
-        final int packetLossRate =
+        final PacketLossCalculationResult calculateResult =
                 mPacketLossCalculator.getPacketLossRatePercentage(
                         mLastIpSecTransformState, state, getLogPrefix());
 
-        if (packetLossRate == PACKET_LOSS_UNAVALAIBLE) {
+        if (calculateResult.getResultType() == PACKET_LOSS_RATE_INVALID) {
             return;
         }
 
         final String logMsg =
-                "packetLossRate: "
-                        + packetLossRate
+                "calculateResult: "
+                        + calculateResult
                         + "% in the past "
                         + (state.getTimestampMillis()
                                 - mLastIpSecTransformState.getTimestampMillis())
                         + "ms";
 
         mLastIpSecTransformState = state;
-        if (packetLossRate < mPacketLossRatePercentThreshold) {
+        if (calculateResult.getPacketLossRatePercent() < mPacketLossRatePercentThreshold) {
             logV(logMsg);
             onValidationResultReceivedInternal(false /* isFailed */);
         } else {
@@ -343,7 +373,7 @@
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public static class PacketLossCalculator {
         /** Calculate the packet loss rate between two timestamps */
-        public int getPacketLossRatePercentage(
+        public PacketLossCalculationResult getPacketLossRatePercentage(
                 @NonNull IpSecTransformState oldState,
                 @NonNull IpSecTransformState newState,
                 String logPrefix) {
@@ -359,7 +389,7 @@
             if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) {
                 // The replay window did not proceed and all packets might have been delivered out
                 // of order
-                return PACKET_LOSS_UNAVALAIBLE;
+                return PacketLossCalculationResult.invalid();
             }
 
             // Get the expected packet count by assuming there is no packet loss. In this case, SA
@@ -386,10 +416,11 @@
                     || actualPktCntDiff < 0
                     || actualPktCntDiff > expectedPktCntDiff) {
                 logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff");
-                return PACKET_LOSS_UNAVALAIBLE;
+                return PacketLossCalculationResult.invalid();
             }
 
-            return 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+            final int percent = 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+            return PacketLossCalculationResult.valid(percent);
         }
     }
 
@@ -409,4 +440,59 @@
     private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) {
         return BitSet.valueOf(state.getReplayBitmap()).cardinality();
     }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class PacketLossCalculationResult {
+        @PacketLossResultType private final int mResultType;
+        private final int mPacketLossRatePercent;
+
+        private PacketLossCalculationResult(@PacketLossResultType int type, int percent) {
+            mResultType = type;
+            mPacketLossRatePercent = percent;
+        }
+
+        /** Construct an instance that contains a valid packet loss rate */
+        public static PacketLossCalculationResult valid(int percent) {
+            return new PacketLossCalculationResult(PACKET_LOSS_RATE_VALID, percent);
+        }
+
+        /** Construct an instance indicating the inability to get a valid packet loss rate */
+        public static PacketLossCalculationResult invalid() {
+            return new PacketLossCalculationResult(
+                    PACKET_LOSS_RATE_INVALID, PACKET_LOSS_PERCENT_UNAVAILABLE);
+        }
+
+        @PacketLossResultType
+        public int getResultType() {
+            return mResultType;
+        }
+
+        public int getPacketLossRatePercent() {
+            return mPacketLossRatePercent;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mResultType, mPacketLossRatePercent);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof PacketLossCalculationResult)) {
+                return false;
+            }
+
+            final PacketLossCalculationResult rhs = (PacketLossCalculationResult) other;
+            return mResultType == rhs.mResultType
+                    && mPacketLossRatePercent == rhs.mPacketLossRatePercent;
+        }
+
+        @Override
+        public String toString() {
+            return "mResultType: "
+                    + mResultType
+                    + " | mPacketLossRatePercent: "
+                    + mPacketLossRatePercent;
+        }
+    }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index ad29392..d51828e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -67,12 +67,11 @@
     private static final int UID_2 = 99;
     private final MockClock mMockClock = new MockClock();
     private final HandlerThread mHandlerThread = new HandlerThread("test");
+    private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
     private Handler mHandler;
     private PowerStats mCollectedStats;
     private PowerProfile mPowerProfile = new PowerProfile();
     @Mock
-    private PowerStatsUidResolver mUidResolver;
-    @Mock
     private CpuPowerStatsCollector.KernelCpuStatsReader mMockKernelCpuStatsReader;
     @Mock
     private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
@@ -144,15 +143,8 @@
         mHandlerThread.start();
         mHandler = mHandlerThread.getThreadHandler();
         when(mMockKernelCpuStatsReader.isSupportedFeature()).thenReturn(true);
-        when(mUidResolver.mapUid(anyInt())).thenAnswer(invocation -> {
-            int uid = invocation.getArgument(0);
-            if (uid == ISOLATED_UID) {
-                return UID_2;
-            } else {
-                return uid;
-            }
-        });
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(anyInt())).thenReturn(new int[0]);
+        mUidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID_2);
     }
 
     @Test
@@ -268,8 +260,7 @@
         mockEnergyConsumers();
 
         CpuPowerStatsCollector collector = createCollector(8, 0);
-        CpuPowerStatsLayout layout =
-                new CpuPowerStatsLayout();
+        CpuPowerStatsLayout layout = new CpuPowerStatsLayout();
         layout.fromExtras(collector.getPowerStatsDescriptor().extras);
 
         mockKernelCpuStats(new long[]{1111, 2222, 3333},
@@ -333,6 +324,45 @@
                 .isEqualTo(78);
     }
 
+    @Test
+    public void isolatedUidReuse() {
+        mockCpuScalingPolicies(1);
+        mockPowerProfile();
+        mockEnergyConsumers();
+
+        CpuPowerStatsCollector collector = createCollector(8, 0);
+        CpuPowerStatsLayout layout = new CpuPowerStatsLayout();
+        layout.fromExtras(collector.getPowerStatsDescriptor().extras);
+
+        mockKernelCpuStats(new long[]{1111, 2222, 3333},
+                new SparseArray<>() {{
+                    put(UID_2, new long[]{100, 150});
+                    put(ISOLATED_UID, new long[]{10000, 20000});
+                }}, 0, 1234);
+
+        mMockClock.uptime = 1000;
+        collector.forceSchedule();
+        waitForIdle();
+
+        mUidResolver.noteIsolatedUidRemoved(ISOLATED_UID, UID_2);
+        mUidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID_2);
+
+        mockKernelCpuStats(new long[]{5555, 4444, 3333},
+                new SparseArray<>() {{
+                    put(UID_2, new long[]{100, 150});
+                    put(ISOLATED_UID, new long[]{245, 528});
+                }}, 1234, 3421);
+
+        mMockClock.uptime = 2000;
+        collector.forceSchedule();
+        waitForIdle();
+
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 0))
+                .isEqualTo(245);
+        assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 1))
+                .isEqualTo(528);
+    }
+
     private void mockCpuScalingPolicies(int clusterCount) {
         SparseArray<int[]> cpus = new SparseArray<>();
         SparseArray<int[]> freqs = new SparseArray<>();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
index df1200b..89d6c1c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
@@ -66,8 +66,7 @@
     public void setup() {
         mHandlerThread.start();
         mHandler = mHandlerThread.getThreadHandler();
-        mCollector = new PowerStatsCollector(mHandler,
-                60000,
+        mCollector = new PowerStatsCollector(mHandler, 60000, mock(PowerStatsUidResolver.class),
                 mMockClock) {
             @Override
             protected PowerStats collectStats() {
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index fdf8fb8..0a83a53 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -19,7 +19,6 @@
 import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY;
 import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
 
-import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.PACKET_LOSS_UNAVALAIBLE;
 import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static org.junit.Assert.assertEquals;
@@ -44,6 +43,7 @@
 import android.os.OutcomeReceiver;
 import android.os.PowerManager;
 
+import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculationResult;
 import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculator;
 import com.android.server.vcn.routeselection.NetworkMetricMonitor.IpSecTransformWrapper;
 import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
@@ -293,7 +293,9 @@
     }
 
     private void checkHandleLossRate(
-            int mockPacketLossRate, boolean isLastStateExpectedToUpdate, boolean isCallbackExpected)
+            PacketLossCalculationResult mockPacketLossRate,
+            boolean isLastStateExpectedToUpdate,
+            boolean isCallbackExpected)
             throws Exception {
         final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
                 startMonitorAndCaptureStateReceiver();
@@ -327,26 +329,32 @@
     @Test
     public void testHandleLossRate_validationPass() throws Exception {
         checkHandleLossRate(
-                2, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+                PacketLossCalculationResult.valid(2),
+                true /* isLastStateExpectedToUpdate */,
+                true /* isCallbackExpected */);
     }
 
     @Test
     public void testHandleLossRate_validationFail() throws Exception {
         checkHandleLossRate(
-                22, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+                PacketLossCalculationResult.valid(22),
+                true /* isLastStateExpectedToUpdate */,
+                true /* isCallbackExpected */);
         verify(mConnectivityManager).reportNetworkConnectivity(mNetwork, false);
     }
 
     @Test
     public void testHandleLossRate_resultUnavalaible() throws Exception {
         checkHandleLossRate(
-                PACKET_LOSS_UNAVALAIBLE,
+                PacketLossCalculationResult.invalid(),
                 false /* isLastStateExpectedToUpdate */,
                 false /* isCallbackExpected */);
     }
 
     private void checkGetPacketLossRate(
-            IpSecTransformState oldState, IpSecTransformState newState, int expectedLossRate)
+            IpSecTransformState oldState,
+            IpSecTransformState newState,
+            PacketLossCalculationResult expectedLossRate)
             throws Exception {
         assertEquals(
                 expectedLossRate,
@@ -362,14 +370,30 @@
             throws Exception {
         final IpSecTransformState newState =
                 newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
+        checkGetPacketLossRate(
+                oldState, newState, PacketLossCalculationResult.valid(expectedDataLossRate));
+    }
+
+    private void checkGetPacketLossRate(
+            IpSecTransformState oldState,
+            int rxSeqNo,
+            int packetCount,
+            int packetInWin,
+            PacketLossCalculationResult expectedDataLossRate)
+            throws Exception {
+        final IpSecTransformState newState =
+                newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
         checkGetPacketLossRate(oldState, newState, expectedDataLossRate);
     }
 
     @Test
     public void testGetPacketLossRate_replayWindowUnchanged() throws Exception {
         checkGetPacketLossRate(
-                mTransformStateInitial, mTransformStateInitial, PACKET_LOSS_UNAVALAIBLE);
-        checkGetPacketLossRate(mTransformStateInitial, 3000, 2000, 2000, PACKET_LOSS_UNAVALAIBLE);
+                mTransformStateInitial,
+                mTransformStateInitial,
+                PacketLossCalculationResult.invalid());
+        checkGetPacketLossRate(
+                mTransformStateInitial, 3000, 2000, 2000, PacketLossCalculationResult.invalid());
     }
 
     @Test