Merge "Fix split overlay by drag drop launch" into tm-qpr-dev
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 61cca00..4fc3254 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1441,11 +1441,6 @@
     }
 
     /** @hide */
-    public void setRemoteTransition(@Nullable RemoteTransition remoteTransition) {
-        mRemoteTransition = remoteTransition;
-    }
-
-    /** @hide */
     public static ActivityOptions fromBundle(Bundle bOptions) {
         return bOptions != null ? new ActivityOptions(bOptions) : null;
     }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index f691300..fbabf52 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -254,7 +254,7 @@
                     SystemUiDeviceConfigFlags.IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP,
                     DEFAULT_IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP);
 
-    private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 125;
+    private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 0;
 
     private static final int URI_PERMISSION_INTENT_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
             | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index b32afb4..66fff5c 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -951,7 +951,7 @@
         protected void onPostExecute(Drawable d) {
             if (getOtherProfile() == mDisplayResolveInfo) {
                 mResolverListCommunicator.updateProfileViewButton();
-            } else {
+            } else if (!mDisplayResolveInfo.hasDisplayIcon()) {
                 mDisplayResolveInfo.setDisplayIcon(d);
                 mHolder.bindIcon(mDisplayResolveInfo);
                 // Notify in case view is already bound to resolve the race conditions on
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 8f10a5e..e625b31 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -55,7 +55,6 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
@@ -155,7 +154,6 @@
 
     // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
     public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
-    public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK = 1;
     public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2;
     public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3;
     public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4;
@@ -226,7 +224,7 @@
     public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = {
             // This should be mapping to CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE.
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK,
+            NO_STATSD_LOGGING, // This is deprecated.
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE,
@@ -310,7 +308,6 @@
     /** @hide */
     @IntDef({
             CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
-            CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK,
             CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
             CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
             CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
@@ -738,8 +735,6 @@
         switch (cujType) {
             case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
                 return "SHADE_EXPAND_COLLAPSE";
-            case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK:
-                return "SHADE_EXPAND_COLLAPSE_LOCK";
             case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
                 return "SHADE_SCROLL_FLING";
             case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index ca40a40..14a6d5e 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -142,6 +142,11 @@
      */
     public static final int ACTION_LOAD_SHARE_SHEET = 16;
 
+    /**
+     * Time it takes to show AOD display after folding the device.
+     */
+    public static final int ACTION_FOLD_TO_AOD = 17;
+
     private static final int[] ACTIONS_ALL = {
         ACTION_EXPAND_PANEL,
         ACTION_TOGGLE_RECENTS,
@@ -160,6 +165,7 @@
         ACTION_UDFPS_ILLUMINATE,
         ACTION_SHOW_BACK_ARROW,
         ACTION_LOAD_SHARE_SHEET,
+        ACTION_FOLD_TO_AOD,
     };
 
     /** @hide */
@@ -181,6 +187,7 @@
         ACTION_UDFPS_ILLUMINATE,
         ACTION_SHOW_BACK_ARROW,
         ACTION_LOAD_SHARE_SHEET,
+        ACTION_FOLD_TO_AOD
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Action {
@@ -204,6 +211,7 @@
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE,
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW,
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET,
+            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD
     };
 
     private static LatencyTracker sLatencyTracker;
@@ -297,6 +305,8 @@
                 return "ACTION_SHOW_BACK_ARROW";
             case 17:
                 return "ACTION_LOAD_SHARE_SHEET";
+            case 19:
+                return "ACTION_FOLD_TO_AOD";
             default:
                 throw new IllegalArgumentException("Invalid action");
         }
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 09ff4e0..9ee9b82 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -828,6 +828,8 @@
                     mSystemGestureExclusionListener, mContext.getDisplayId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Failed to unregister window manager callbacks", e);
         }
     }
 
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index c6d9eba..f9bfd58 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1069,6 +1069,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1075136930": {
+      "message": "startLockTaskMode: Can't lock due to auth",
+      "level": "WARN",
+      "group": "WM_DEBUG_LOCKTASK",
+      "at": "com\/android\/server\/wm\/LockTaskController.java"
+    },
     "-1069336896": {
       "message": "onRootTaskOrderChanged(): rootTask=%s",
       "level": "DEBUG",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 420d606..586e3a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -495,14 +495,15 @@
                                         mPipBoundsState.getBounds(),
                                         mPipBoundsState.getAspectRatio());
                         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
-                        mPipTaskOrganizer.scheduleAnimateResizePip(destinationBounds,
-                                mEnterAnimationDuration,
-                                null /* updateBoundsCallback */);
-
-                        mTouchHandler.onAspectRatioChanged();
-                        updateMovementBounds(null /* toBounds */, false /* fromRotation */,
-                                false /* fromImeAdjustment */, false /* fromShelfAdjustment */,
-                                null /* windowContainerTransaction */);
+                        if (!destinationBounds.equals(mPipBoundsState.getBounds())) {
+                            mPipTaskOrganizer.scheduleAnimateResizePip(destinationBounds,
+                                    mEnterAnimationDuration,
+                                    null /* updateBoundsCallback */);
+                            mTouchHandler.onAspectRatioChanged();
+                            updateMovementBounds(null /* toBounds */, false /* fromRotation */,
+                                    false /* fromImeAdjustment */, false /* fromShelfAdjustment */,
+                                    null /* windowContainerTransaction */);
+                        }
                     }
 
                     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index afc70a44..08eb2c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -154,8 +154,6 @@
 
     private final int mCurrentUserId;
 
-    private ScreenRotationAnimation mRotationAnimation;
-
     private Drawable mEnterpriseThumbnailDrawable;
 
     private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() {
@@ -340,12 +338,6 @@
 
         final Runnable onAnimFinish = () -> {
             if (!animations.isEmpty()) return;
-
-            if (mRotationAnimation != null) {
-                mRotationAnimation.kill();
-                mRotationAnimation = null;
-            }
-
             mAnimations.remove(transition);
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
         };
@@ -365,11 +357,8 @@
                     isSeamlessDisplayChange = isRotationSeamless(info, mDisplayController);
                     final int anim = getRotationAnimation(info);
                     if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
-                        mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
-                                mTransactionPool, startTransaction, change, info.getRootLeash(),
-                                anim);
-                        mRotationAnimation.startAnimation(animations, onAnimFinish,
-                                mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
+                        startRotationAnimation(startTransaction, change, info, anim, animations,
+                                onAnimFinish);
                         continue;
                     }
                 } else {
@@ -413,6 +402,13 @@
                     startTransaction.setWindowCrop(change.getLeash(),
                             change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
                 }
+                // Rotation change of independent non display window container.
+                if (change.getParent() == null
+                        && change.getStartRotation() != change.getEndRotation()) {
+                    startRotationAnimation(startTransaction, change, info,
+                            ROTATION_ANIMATION_ROTATE, animations, onAnimFinish);
+                    continue;
+                }
             }
 
             // Don't animate anything that isn't independent.
@@ -543,6 +539,31 @@
         }
     }
 
+    private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
+            TransitionInfo.Change change, TransitionInfo info, int animHint,
+            ArrayList<Animator> animations, Runnable onAnimFinish) {
+        final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession,
+                mTransactionPool, startTransaction, change, info.getRootLeash(), animHint);
+        // The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
+        // content, and background color. The item of "animGroup" will be removed if the sub
+        // animation is finished. Then if the list becomes empty, the rotation animation is done.
+        final ArrayList<Animator> animGroup = new ArrayList<>(3);
+        final ArrayList<Animator> animGroupStore = new ArrayList<>(3);
+        final Runnable finishCallback = () -> {
+            if (!animGroup.isEmpty()) return;
+            anim.kill();
+            animations.removeAll(animGroupStore);
+            onAnimFinish.run();
+        };
+        anim.startAnimation(animGroup, finishCallback, mTransitionAnimationScaleSetting,
+                mMainExecutor, mAnimExecutor);
+        for (int i = animGroup.size() - 1; i >= 0; i--) {
+            final Animator animator = animGroup.get(i);
+            animGroupStore.add(animator);
+            animations.add(animator);
+        }
+    }
+
     private void edgeExtendWindow(TransitionInfo.Change change,
             Animation a, SurfaceControl.Transaction startTransaction,
             SurfaceControl.Transaction finishTransaction) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 1005ef1..a843b2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -84,7 +84,7 @@
     private final Context mContext;
     private final TransactionPool mTransactionPool;
     private final float[] mTmpFloats = new float[9];
-    /** The leash of display. */
+    /** The leash of the changing window container. */
     private final SurfaceControl mSurfaceControl;
     private final Rect mStartBounds = new Rect();
     private final Rect mEndBounds = new Rect();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index e6006c4..cd29741 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -356,7 +356,7 @@
             // Put all the OPEN/SHOW on top
             if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
                 // Wallpaper is always at the bottom.
-                layer = 0;
+                layer = -zSplitLine;
             } else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
                 if (isOpening) {
                     // put on top
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index fef7383..1c57480 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -75,12 +75,12 @@
         }
     private var radius: Float = 0f
         set(value) {
-            rippleShader.radius = value
+            rippleShader.setMaxSize(value * 2f, value * 2f)
             field = value
         }
     private var origin: PointF = PointF()
         set(value) {
-            rippleShader.origin = value
+            rippleShader.setCenter(value.x, value.y)
             field = value
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 8292e52..da675de 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.graphics.PixelFormat
-import android.graphics.PointF
 import android.os.SystemProperties
 import android.util.DisplayMetrics
 import android.view.View
@@ -85,7 +84,7 @@
     private var debounceLevel = 0
 
     @VisibleForTesting
-    var rippleView: RippleView = RippleView(context, attrs = null)
+    var rippleView: RippleView = RippleView(context, attrs = null).also { it.setupShader() }
 
     init {
         pluggedIn = batteryController.isPluggedIn
@@ -177,20 +176,25 @@
         context.display.getRealMetrics(displayMetrics)
         val width = displayMetrics.widthPixels
         val height = displayMetrics.heightPixels
-        rippleView.radius = Integer.max(width, height).toFloat()
-        rippleView.origin = when (RotationUtils.getExactRotation(context)) {
+        val maxDiameter = Integer.max(width, height) * 2f
+        rippleView.setMaxSize(maxDiameter, maxDiameter)
+        when (RotationUtils.getExactRotation(context)) {
             RotationUtils.ROTATION_LANDSCAPE -> {
-                PointF(width * normalizedPortPosY, height * (1 - normalizedPortPosX))
+                rippleView.setCenter(
+                        width * normalizedPortPosY, height * (1 - normalizedPortPosX))
             }
             RotationUtils.ROTATION_UPSIDE_DOWN -> {
-                PointF(width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY))
+                rippleView.setCenter(
+                        width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY))
             }
             RotationUtils.ROTATION_SEASCAPE -> {
-                PointF(width * (1 - normalizedPortPosY), height * normalizedPortPosX)
+                rippleView.setCenter(
+                        width * (1 - normalizedPortPosY), height * normalizedPortPosX)
             }
             else -> {
                 // ROTATION_NONE
-                PointF(width * normalizedPortPosX, height * normalizedPortPosY)
+                rippleView.setCenter(
+                        width * normalizedPortPosX, height * normalizedPortPosY)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index f6368ee..65400c2 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -21,7 +21,6 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Color;
-import android.graphics.PointF;
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
@@ -34,6 +33,7 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
+import com.android.systemui.ripple.RippleShader;
 import com.android.systemui.ripple.RippleView;
 
 import java.text.NumberFormat;
@@ -138,6 +138,8 @@
         animatorSetScrim.start();
 
         mRippleView = findViewById(R.id.wireless_charging_ripple);
+        // TODO: Make rounded box shape if the device is tablet.
+        mRippleView.setupShader(RippleShader.RippleShape.CIRCLE);
         OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View view) {
@@ -230,11 +232,11 @@
         if (mRippleView != null) {
             int width = getMeasuredWidth();
             int height = getMeasuredHeight();
-            mRippleView.setColor(
-                    Utils.getColorAttr(mRippleView.getContext(),
-                            android.R.attr.colorAccent).getDefaultColor());
-            mRippleView.setOrigin(new PointF(width / 2, height / 2));
-            mRippleView.setRadius(Math.max(width, height) * 0.5f);
+            mRippleView.setCenter(width * 0.5f, height * 0.5f);
+            float maxSize = Math.max(width, height);
+            mRippleView.setMaxSize(maxSize, maxSize);
+            mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
+                    android.R.attr.colorAccent).getDefaultColor());
         }
 
         super.onLayout(changed, left, top, right, bottom);
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 495f697..0f1ae00 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -19,7 +19,6 @@
 import android.annotation.SuppressLint
 import android.app.StatusBarManager
 import android.content.Context
-import android.graphics.PointF
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.media.MediaRoute2Info
@@ -202,10 +201,10 @@
         val height = windowBounds.height()
         val width = windowBounds.width()
 
-        rippleView.radius = height / 5f
+        val maxDiameter = height / 2.5f
+        rippleView.setMaxSize(maxDiameter, maxDiameter)
         // Center the ripple on the bottom of the screen in the middle.
-        rippleView.origin = PointF(/* x= */ width / 2f, /* y= */ height.toFloat())
-
+        rippleView.setCenter(width * 0.5f, height.toFloat())
         val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
         val colorWithAlpha = ColorUtils.setAlphaComponent(color, 70)
         rippleView.setColor(colorWithAlpha)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index fed546bf..6a505f0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -23,10 +23,10 @@
 /**
  * An expanding ripple effect for the media tap-to-transfer receiver chip.
  */
-class ReceiverChipRippleView(
-        context: Context?, attrs: AttributeSet?
-) : RippleView(context, attrs) {
+class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
     init {
+        // TODO: use RippleShape#ELLIPSE when calling setupShader.
+        setupShader()
         setRippleFill(true)
         duration = 3000L
     }
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index 93a2efc..0a8e6e2 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -20,92 +20,107 @@
 import android.util.MathUtils
 
 /**
- * Shader class that renders an expanding charging ripple effect. A charging ripple contains
- * three elements:
- * 1. an expanding filled circle that appears in the beginning and quickly fades away
+ * Shader class that renders an expanding ripple effect. The ripple contains three elements:
+ *
+ * 1. an expanding filled [RippleShape] that appears in the beginning and quickly fades away
  * 2. an expanding ring that appears throughout the effect
  * 3. an expanding ring-shaped area that reveals noise over #2.
  *
+ * The ripple shader will be default to the circle shape if not specified.
+ *
  * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
  */
-class RippleShader internal constructor() : RuntimeShader(SHADER) {
+class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.CIRCLE) :
+        RuntimeShader(buildShader(rippleShape)) {
+
+    /** Shapes that the [RippleShader] supports. */
+    enum class RippleShape {
+        CIRCLE,
+        ROUNDED_BOX,
+        ELLIPSE
+    }
+
     companion object {
-        private const val SHADER_UNIFORMS = """uniform vec2 in_origin;
+        private const val SHADER_UNIFORMS = """uniform vec2 in_center;
+                uniform vec2 in_size;
                 uniform float in_progress;
-                uniform float in_maxRadius;
+                uniform float in_cornerRadius;
+                uniform float in_thickness;
                 uniform float in_time;
                 uniform float in_distort_radial;
                 uniform float in_distort_xy;
-                uniform float in_radius;
                 uniform float in_fadeSparkle;
-                uniform float in_fadeCircle;
+                uniform float in_fadeFill;
                 uniform float in_fadeRing;
                 uniform float in_blur;
                 uniform float in_pixelDensity;
                 layout(color) uniform vec4 in_color;
                 uniform float in_sparkle_strength;"""
-        private const val SHADER_LIB = """float triangleNoise(vec2 n) {
-                    n  = fract(n * vec2(5.3987, 5.4421));
-                    n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
-                    float xy = n.x * n.y;
-                    return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+
+        private const val SHADER_CIRCLE_MAIN = """vec4 main(vec2 p) {
+                vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
+                float radius = in_size.x * 0.5;
+                float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
+                float inside = soften(sdCircle(p_distorted-in_center, radius * 1.2), in_blur);
+                float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+                    * (1.-sparkleRing) * in_fadeSparkle;
+
+                float rippleInsideAlpha = (1.-inside) * in_fadeFill;
+                float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+                vec4 ripple = in_color * rippleAlpha;
+                return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
+            }
+        """
+
+        private const val SHADER_ROUNDED_BOX_MAIN = """vec4 main(vec2 p) {
+                float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
+                    in_thickness), in_blur);
+                float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
+                    in_blur);
+                float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+                    * (1.-sparkleRing) * in_fadeSparkle;
+
+                float rippleInsideAlpha = (1.-inside) * in_fadeFill;
+                float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+                vec4 ripple = in_color * rippleAlpha;
+                return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
+            }
+        """
+
+        private const val SHADER_ELLIPSE_MAIN = """vec4 main(vec2 p) {
+                vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
+
+                float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
+                float inside = soften(sdEllipse(p_distorted-in_center, in_size * 1.2), in_blur);
+                float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+                    * (1.-sparkleRing) * in_fadeSparkle;
+
+                float rippleInsideAlpha = (1.-inside) * in_fadeFill;
+                float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+                vec4 ripple = in_color * rippleAlpha;
+                return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
+            }
+        """
+
+        private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
+                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.CIRCLE_SDF +
+                SHADER_CIRCLE_MAIN
+        private const val ROUNDED_BOX_SHADER = SHADER_UNIFORMS +
+                RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+                SdfShaderLibrary.ROUNDED_BOX_SDF + SHADER_ROUNDED_BOX_MAIN
+        private const val ELLIPSE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
+                SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.ELLIPSE_SDF +
+                SHADER_ELLIPSE_MAIN
+
+        private fun buildShader(rippleShape: RippleShape): String =
+                when (rippleShape) {
+                    RippleShape.CIRCLE -> CIRCLE_SHADER
+                    RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
+                    RippleShape.ELLIPSE -> ELLIPSE_SHADER
                 }
-                const float PI = 3.1415926535897932384626;
-
-                float threshold(float v, float l, float h) {
-                  return step(l, v) * (1.0 - step(h, v));
-                }
-
-                float sparkles(vec2 uv, float t) {
-                  float n = triangleNoise(uv);
-                  float s = 0.0;
-                  for (float i = 0; i < 4; i += 1) {
-                    float l = i * 0.01;
-                    float h = l + 0.1;
-                    float o = smoothstep(n - l, h, n);
-                    o *= abs(sin(PI * o * (t + 0.55 * i)));
-                    s += o;
-                  }
-                  return s;
-                }
-
-                float softCircle(vec2 uv, vec2 xy, float radius, float blur) {
-                  float blurHalf = blur * 0.5;
-                  float d = distance(uv, xy);
-                  return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);
-                }
-
-                float softRing(vec2 uv, vec2 xy, float radius, float blur) {
-                  float thickness_half = radius * 0.25;
-                  float circle_outer = softCircle(uv, xy, radius + thickness_half, blur);
-                  float circle_inner = softCircle(uv, xy, radius - thickness_half, blur);
-                  return circle_outer - circle_inner;
-                }
-
-                vec2 distort(vec2 p, vec2 origin, float time,
-                    float distort_amount_radial, float distort_amount_xy) {
-                    float2 distance = origin - p;
-                    float angle = atan(distance.y, distance.x);
-                    return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
-                                    cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
-                             + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
-                                    cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
-                }"""
-        private const val SHADER_MAIN = """vec4 main(vec2 p) {
-                    vec2 p_distorted = distort(p, in_origin, in_time, in_distort_radial,
-                        in_distort_xy);
-
-                    // Draw shapes
-                    float sparkleRing = softRing(p_distorted, in_origin, in_radius, in_blur);
-                    float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
-                        * sparkleRing * in_fadeSparkle;
-                    float circle = softCircle(p_distorted, in_origin, in_radius * 1.2, in_blur);
-                    float rippleAlpha = max(circle * in_fadeCircle,
-                        softRing(p_distorted, in_origin, in_radius, in_blur) * in_fadeRing) * 0.45;
-                    vec4 ripple = in_color * rippleAlpha;
-                    return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
-                }"""
-        private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN
 
         private fun subProgress(start: Float, end: Float, progress: Float): Float {
             val min = Math.min(start, end)
@@ -116,22 +131,18 @@
     }
 
     /**
-     * Maximum radius of the ripple.
+     * Sets the center position of the ripple.
      */
-    var radius: Float = 0.0f
-        set(value) {
-            field = value
-            setFloatUniform("in_maxRadius", value)
-        }
+    fun setCenter(x: Float, y: Float) {
+        setFloatUniform("in_center", x, y)
+    }
 
-    /**
-     * Origin coordinate of the ripple.
-     */
-    var origin: PointF = PointF()
-        set(value) {
-            field = value
-            setFloatUniform("in_origin", value.x, value.y)
-        }
+    /** Max width of the ripple. */
+    private var maxSize: PointF = PointF()
+    fun setMaxSize(width: Float, height: Float) {
+        maxSize.x = width
+        maxSize.y = height
+    }
 
     /**
      * Progress of the ripple. Float value between [0, 1].
@@ -140,20 +151,27 @@
         set(value) {
             field = value
             setFloatUniform("in_progress", value)
-            setFloatUniform("in_radius",
-                    (1 - (1 - value) * (1 - value) * (1 - value))* radius)
+            val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
+
+            setFloatUniform("in_size", /* width= */ maxSize.x * curvedProg,
+                    /* height= */ maxSize.y * curvedProg)
+            setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
+            // radius should not exceed width and height values.
+            setFloatUniform("in_cornerRadius",
+                    Math.min(maxSize.x, maxSize.y) * curvedProg)
+
             setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
 
             val fadeIn = subProgress(0f, 0.1f, value)
             val fadeOutNoise = subProgress(0.4f, 1f, value)
             var fadeOutRipple = 0f
-            var fadeCircle = 0f
+            var fadeFill = 0f
             if (!rippleFill) {
-                fadeCircle = subProgress(0f, 0.2f, value)
+                fadeFill = subProgress(0f, 0.6f, value)
                 fadeOutRipple = subProgress(0.3f, 1f, value)
             }
             setFloatUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
-            setFloatUniform("in_fadeCircle", 1 - fadeCircle)
+            setFloatUniform("in_fadeFill", 1 - fadeFill)
             setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
         }
 
@@ -169,7 +187,7 @@
     /**
      * A hex value representing the ripple color, in the format of ARGB
      */
-    var color: Int = 0xffffff.toInt()
+    var color: Int = 0xffffff
         set(value) {
             field = value
             setColorUniform("in_color", value)
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
new file mode 100644
index 0000000..0cacbc2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.systemui.ripple
+
+/** A common utility functions that are used for computing [RippleShader]. */
+class RippleShaderUtilLibrary {
+    companion object {
+        const val SHADER_LIB = """
+            float triangleNoise(vec2 n) {
+                    n  = fract(n * vec2(5.3987, 5.4421));
+                    n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
+                    float xy = n.x * n.y;
+                    return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+                }
+                const float PI = 3.1415926535897932384626;
+
+                float sparkles(vec2 uv, float t) {
+                    float n = triangleNoise(uv);
+                    float s = 0.0;
+                    for (float i = 0; i < 4; i += 1) {
+                        float l = i * 0.01;
+                        float h = l + 0.1;
+                        float o = smoothstep(n - l, h, n);
+                        o *= abs(sin(PI * o * (t + 0.55 * i)));
+                        s += o;
+                    }
+                    return s;
+                }
+
+                vec2 distort(vec2 p, float time, float distort_amount_radial,
+                    float distort_amount_xy) {
+                        float angle = atan(p.y, p.x);
+                          return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
+                                    cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+                             + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
+                                    cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
+            }"""
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index fc52464..83d9f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -23,39 +23,42 @@
 import android.content.res.Configuration
 import android.graphics.Canvas
 import android.graphics.Paint
-import android.graphics.PointF
 import android.util.AttributeSet
 import android.view.View
+import com.android.systemui.ripple.RippleShader.RippleShape
 
 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
+private const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt()
 
 /**
- * A generic expanding ripple effect. To trigger the ripple expansion, set [radius] and [origin],
- * then call [startRipple].
+ * A generic expanding ripple effect.
+ *
+ * Set up the shader with a desired [RippleShape] using [setupShader], [setMaxSize] and [setCenter],
+ * then call [startRipple] to trigger the ripple expansion.
  */
 open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
-    private val rippleShader = RippleShader()
-    private val defaultColor: Int = 0xffffffff.toInt()
+
+    private lateinit var rippleShader: RippleShader
+    private lateinit var rippleShape: RippleShape
     private val ripplePaint = Paint()
 
     var rippleInProgress: Boolean = false
-    var radius: Float = 0.0f
-        set(value) {
-            rippleShader.radius = value
-            field = value
-        }
-    var origin: PointF = PointF()
-        set(value) {
-            rippleShader.origin = value
-            field = value
-        }
     var duration: Long = 1750
 
-    init {
-        rippleShader.color = defaultColor
-        rippleShader.progress = 0f
-        rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
-        ripplePaint.shader = rippleShader
+    private var maxWidth: Float = 0.0f
+    private var maxHeight: Float = 0.0f
+    fun setMaxSize(maxWidth: Float, maxHeight: Float) {
+        this.maxWidth = maxWidth
+        this.maxHeight = maxHeight
+        rippleShader.setMaxSize(maxWidth, maxHeight)
+    }
+
+    private var centerX: Float = 0.0f
+    private var centerY: Float = 0.0f
+    fun setCenter(x: Float, y: Float) {
+        this.centerX = x
+        this.centerY = y
+        rippleShader.setCenter(x, y)
     }
 
     override fun onConfigurationChanged(newConfig: Configuration?) {
@@ -68,6 +71,18 @@
         super.onAttachedToWindow()
     }
 
+    /** Initializes the shader. Must be called before [startRipple]. */
+    fun setupShader(rippleShape: RippleShape = RippleShape.CIRCLE) {
+        this.rippleShape = rippleShape
+        rippleShader = RippleShader(rippleShape)
+
+        rippleShader.color = RIPPLE_DEFAULT_COLOR
+        rippleShader.progress = 0f
+        rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
+
+        ripplePaint.shader = rippleShader
+    }
+
     @JvmOverloads
     fun startRipple(onAnimationEnd: Runnable? = null) {
         if (rippleInProgress) {
@@ -113,11 +128,24 @@
             // if it's unsupported.
             return
         }
-        // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
-        // the active effect area. Values here should be kept in sync with the
-        // animation implementation in the ripple shader.
-        val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
-                (1 - rippleShader.progress)) * radius * 2
-        canvas.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
+        // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the
+        // active effect area. Values here should be kept in sync with the animation implementation
+        // in the ripple shader.
+        if (rippleShape == RippleShape.CIRCLE) {
+            val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
+                    (1 - rippleShader.progress)) * maxWidth
+            canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
+        } else {
+            val maskWidth = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
+                    (1 - rippleShader.progress)) * maxWidth * 2
+            val maskHeight = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
+                    (1 - rippleShader.progress)) * maxHeight * 2
+            canvas.drawRect(
+                    /* left= */ centerX - maskWidth,
+                    /* top= */ centerY - maskHeight,
+                    /* right= */ centerX + maskWidth,
+                    /* bottom= */ centerY + maskHeight,
+                    ripplePaint)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
new file mode 100644
index 0000000..7f26146
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 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.systemui.ripple
+
+/** Library class that contains 2D signed distance functions. */
+class SdfShaderLibrary {
+    companion object {
+        const val CIRCLE_SDF = """
+            float sdCircle(vec2 p, float r) {
+                return (length(p)-r) / r;
+            }
+
+            float circleRing(vec2 p, float radius) {
+                float thicknessHalf = radius * 0.25;
+
+                float outerCircle = sdCircle(p, radius + thicknessHalf);
+                float innerCircle = sdCircle(p, radius);
+
+                return subtract(outerCircle, innerCircle);
+            }
+        """
+
+        const val ROUNDED_BOX_SDF = """
+            float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
+                size *= 0.5;
+                cornerRadius *= 0.5;
+                vec2 d = abs(p)-size+cornerRadius;
+
+                float outside = length(max(d, 0.0));
+                float inside = min(max(d.x, d.y), 0.0);
+
+                return (outside+inside-cornerRadius)/size.y;
+            }
+
+            float roundedBoxRing(vec2 p, vec2 size, float cornerRadius,
+                float borderThickness) {
+                float outerRoundBox = sdRoundedBox(p, size, cornerRadius);
+                float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness),
+                    cornerRadius - borderThickness);
+                return subtract(outerRoundBox, innerRoundBox);
+            }
+        """
+
+        // Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
+        // This is more expensive than the regular circle SDF, recommend to use the circle SDF if
+        // possible.
+        const val ELLIPSE_SDF = """float sdEllipse(vec2 p, vec2 wh) {
+            wh *= 0.5;
+
+            // symmetry
+            (wh.x > wh.y) ? wh = wh.yx, p = abs(p.yx) : p = abs(p);
+
+            vec2 u = wh*p, v = wh*wh;
+
+            float U1 = u.y/2.0;  float U5 = 4.0*U1;
+            float U2 = v.y-v.x;  float U6 = 6.0*U1;
+            float U3 = u.x-U2;   float U7 = 3.0*U3;
+            float U4 = u.x+U2;
+
+            float t = 0.5;
+            for (int i = 0; i < 3; i ++) {
+                float F1 = t*(t*t*(U1*t+U3)+U4)-U1;
+                float F2 = t*t*(U5*t+U7)+U4;
+                float F3 = t*(U6*t+U7);
+
+                t += (F1*F2)/(F1*F3-F2*F2);
+            }
+
+            t = clamp(t, 0.0, 1.0);
+
+            float d = distance(p, wh*vec2(1.0-t*t,2.0*t)/(t*t+1.0));
+            d /= wh.y;
+
+            return (dot(p/wh,p/wh)>1.0) ? d : -d;
+        }
+
+        float ellipseRing(vec2 p, vec2 wh) {
+            vec2 thicknessHalf = wh * 0.25;
+
+            float outerEllipse = sdEllipse(p, wh + thicknessHalf);
+            float innerEllipse = sdEllipse(p, wh);
+
+            return subtract(outerEllipse, innerEllipse);
+        }
+        """
+
+        const val SHADER_SDF_OPERATION_LIB = """
+            float soften(float d, float blur) {
+                float blurHalf = blur * 0.5;
+                return smoothstep(-blurHalf, blurHalf, d);
+            }
+
+            float subtract(float outer, float inner) {
+                return max(outer, -inner);
+            }
+        """
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 4e74540f..e36b67e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -44,7 +44,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
-import android.app.ActivityManager;
 import android.app.Fragment;
 import android.app.StatusBarManager;
 import android.content.ContentResolver;
@@ -288,7 +287,6 @@
     private final NotificationPanelView mView;
     private final VibratorHelper mVibratorHelper;
     private final MetricsLogger mMetricsLogger;
-    private final ActivityManager mActivityManager;
     private final ConfigurationController mConfigurationController;
     private final Provider<FlingAnimationUtils.Builder> mFlingAnimationUtilsBuilder;
     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@@ -341,14 +339,14 @@
     private final RecordingController mRecordingController;
     private final PanelEventsEmitter mPanelEventsEmitter;
     private boolean mSplitShadeEnabled;
-    // The bottom padding reserved for elements of the keyguard measuring notifications
+    /** The bottom padding reserved for elements of the keyguard measuring notifications. */
     private float mKeyguardNotificationBottomPadding;
     /**
      * The top padding from where notification should start in lockscreen.
      * Should be static also during animations and should match the Y of the first notification.
      */
     private float mKeyguardNotificationTopPadding;
-    // Current max allowed keyguard notifications determined by measuring the panel
+    /** Current max allowed keyguard notifications determined by measuring the panel. */
     private int mMaxAllowedKeyguardNotifications;
 
     private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
@@ -728,7 +726,6 @@
             AccessibilityManager accessibilityManager, @DisplayId int displayId,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             MetricsLogger metricsLogger,
-            ActivityManager activityManager,
             ConfigurationController configurationController,
             Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -798,7 +795,6 @@
                 panelExpansionStateManager,
                 ambientState,
                 interactionJankMonitor,
-                keyguardUnlockAnimationController,
                 systemClock);
         mView = view;
         mVibratorHelper = vibratorHelper;
@@ -808,7 +804,6 @@
         mQRCodeScannerController = qrCodeScannerController;
         mControlsComponent = controlsComponent;
         mMetricsLogger = metricsLogger;
-        mActivityManager = activityManager;
         mConfigurationController = configurationController;
         mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
         mMediaHierarchyManager = mediaHierarchyManager;
@@ -3881,29 +3876,37 @@
     }
 
     /**
-     * Starts fold to AOD animation
+     * Starts fold to AOD animation.
+     *
+     * @param startAction invoked when the animation starts.
+     * @param endAction invoked when the animation finishes, also if it was cancelled.
+     * @param cancelAction invoked when the animation is cancelled, before endAction.
      */
-    public void startFoldToAodAnimation(Runnable endAction) {
+    public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
+            Runnable cancelAction) {
         mView.animate()
-                .translationX(0)
-                .alpha(1f)
-                .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
-                .setInterpolator(EMPHASIZED_DECELERATE)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationCancel(Animator animation) {
-                        endAction.run();
-                    }
+            .translationX(0)
+            .alpha(1f)
+            .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+            .setInterpolator(EMPHASIZED_DECELERATE)
+            .setListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    startAction.run();
+                }
 
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        endAction.run();
-                    }
-                })
-                .setUpdateListener(anim -> {
-                    mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
-                })
-                .start();
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    cancelAction.run();
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    endAction.run();
+                }
+            }).setUpdateListener(anim -> {
+                mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
+            }).start();
     }
 
     /**
@@ -3961,7 +3964,7 @@
      * notification data being displayed. In the new notification pipeline, this is handled in
      * {@link ShadeViewManager}.
      */
-    public void updateNotificationViews(String reason) {
+    public void updateNotificationViews() {
         mNotificationStackScrollLayoutController.updateFooter();
 
         mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList());
@@ -4418,7 +4421,7 @@
             NotificationStackScrollLayout.OnEmptySpaceClickListener {
         @Override
         public void onEmptySpaceClicked(float x, float y) {
-            onEmptySpaceClick(x);
+            onEmptySpaceClick();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 15e1129..1d92105 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -89,7 +89,6 @@
         Dumpable, ConfigurationListener {
 
     private static final String TAG = "NotificationShadeWindowController";
-    private static final boolean DEBUG = false;
 
     private final Context mContext;
     private final WindowManager mWindowManager;
@@ -190,7 +189,7 @@
                 return;
             }
         }
-        mCallbacks.add(new WeakReference<StatusBarWindowCallback>(callback));
+        mCallbacks.add(new WeakReference<>(callback));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 121d69d..e52170e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -18,6 +18,8 @@
 
 import static android.view.WindowInsets.Type.systemBars;
 
+import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
+
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.annotation.LayoutRes;
@@ -52,14 +54,12 @@
 import com.android.internal.view.FloatingActionMode;
 import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 /**
  * Combined keyguard and notification panel view. Also holding backdrop and scrims.
  */
 public class NotificationShadeWindowView extends FrameLayout {
     public static final String TAG = "NotificationShadeWindowView";
-    public static final boolean DEBUG = CentralSurfaces.DEBUG;
 
     private int mRightInset = 0;
     private int mLeftInset = 0;
@@ -221,7 +221,7 @@
         }
     }
 
-    class LayoutParams extends FrameLayout.LayoutParams {
+    private static class LayoutParams extends FrameLayout.LayoutParams {
 
         public boolean ignoreRightInset;
 
@@ -243,7 +243,7 @@
     public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback,
             int type) {
         if (type == ActionMode.TYPE_FLOATING) {
-            return startActionMode(originalView, callback, type);
+            return startActionMode(originalView, callback);
         }
         return super.startActionModeForChild(originalView, callback, type);
     }
@@ -258,14 +258,10 @@
         final FloatingActionMode mode =
                 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar);
         mFloatingActionModeOriginatingView = originatingView;
-        mFloatingToolbarPreDrawListener =
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        mode.updateViewLocationInWindow();
-                        return true;
-                    }
-                };
+        mFloatingToolbarPreDrawListener = () -> {
+            mode.updateViewLocationInWindow();
+            return true;
+        };
         return mode;
     }
 
@@ -292,10 +288,10 @@
     }
 
     private ActionMode startActionMode(
-            View originatingView, ActionMode.Callback callback, int type) {
+            View originatingView, ActionMode.Callback callback) {
         ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
         ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback);
-        if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
+        if (wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
             setHandledFloatingActionMode(mode);
         } else {
             mode = null;
@@ -382,7 +378,7 @@
     /**
      * Minimal window to satisfy FloatingToolbar.
      */
-    private Window mFakeWindow = new Window(mContext) {
+    private final Window mFakeWindow = new Window(mContext) {
         @Override
         public void takeSurface(SurfaceHolder.Callback2 callback) {
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 587e0e6d..02316b7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -48,13 +48,12 @@
     private View mStackScroller;
     private View mKeyguardStatusBar;
 
-    private ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
-    private ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
+    private final ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
+    private final ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
     private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild);
     private Consumer<WindowInsets> mInsetsChangedListener = insets -> {};
     private Consumer<QS> mQSFragmentAttachedListener = qs -> {};
     private QS mQs;
-    private View mQSScrollView;
     private View mQSContainer;
 
     @Nullable
@@ -76,7 +75,6 @@
     public void onFragmentViewCreated(String tag, Fragment fragment) {
         mQs = (QS) fragment;
         mQSFragmentAttachedListener.accept(mQs);
-        mQSScrollView = mQs.getView().findViewById(R.id.expanded_qs_scroll_view);
         mQSContainer = mQs.getView().findViewById(R.id.quick_settings_container);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelView.java b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
index 1082967..efff0db 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
@@ -49,10 +49,6 @@
         super(context, attrs, defStyleAttr);
     }
 
-    public PanelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
     public void setOnTouchListener(PanelViewController.TouchHandler touchHandler) {
         super.setOnTouchListener(touchHandler);
         mTouchHandler = touchHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index 229acf4..1a8a6d1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -19,11 +19,11 @@
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
 import static com.android.systemui.classifier.Classifier.GENERIC;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.shade.PanelView.DEBUG;
 
 import static java.lang.Float.isNaN;
 
@@ -53,7 +53,6 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.doze.DozeLog;
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -77,7 +76,6 @@
 import java.util.List;
 
 public abstract class PanelViewController {
-    public static final boolean DEBUG = PanelView.DEBUG;
     public static final String TAG = PanelView.class.getSimpleName();
     public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
     public static final float FLING_SPEED_UP_FACTOR = 0.6f;
@@ -97,7 +95,7 @@
     protected boolean mTouchSlopExceededBeforeDown;
     private float mMinExpandHeight;
     private boolean mPanelUpdateWhenAnimatorEnds;
-    private boolean mVibrateOnOpening;
+    private final boolean mVibrateOnOpening;
     protected boolean mIsLaunchAnimationRunning;
     private int mFixedDuration = NO_FIXED_DURATION;
     protected float mOverExpansion;
@@ -144,7 +142,6 @@
     private int mTouchSlop;
     private float mSlopMultiplier;
     protected boolean mHintAnimationRunning;
-    private boolean mOverExpandedBeforeFling;
     private boolean mTouchAboveFalsingThreshold;
     private int mUnlockFalsingThreshold;
     private boolean mTouchStartedInEmptyArea;
@@ -155,9 +152,9 @@
 
     private ValueAnimator mHeightAnimator;
     private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
-    private FlingAnimationUtils mFlingAnimationUtils;
-    private FlingAnimationUtils mFlingAnimationUtilsClosing;
-    private FlingAnimationUtils mFlingAnimationUtilsDismissing;
+    private final FlingAnimationUtils mFlingAnimationUtils;
+    private final FlingAnimationUtils mFlingAnimationUtilsClosing;
+    private final FlingAnimationUtils mFlingAnimationUtilsDismissing;
     private final LatencyTracker mLatencyTracker;
     private final FalsingManager mFalsingManager;
     private final DozeLog mDozeLog;
@@ -178,9 +175,9 @@
     /**
      * Whether or not the PanelView can be expanded or collapsed with a drag.
      */
-    private boolean mNotificationsDragEnabled;
+    private final boolean mNotificationsDragEnabled;
 
-    private Interpolator mBounceInterpolator;
+    private final Interpolator mBounceInterpolator;
     protected KeyguardBottomAreaView mKeyguardBottomArea;
 
     /**
@@ -201,7 +198,6 @@
     protected final AmbientState mAmbientState;
     protected final LockscreenGestureLogger mLockscreenGestureLogger;
     private final PanelExpansionStateManager mPanelExpansionStateManager;
-    private final TouchHandler mTouchHandler;
     private final InteractionJankMonitor mInteractionJankMonitor;
     protected final SystemClock mSystemClock;
 
@@ -229,8 +225,6 @@
         return mAmbientState;
     }
 
-    private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
-
     public PanelViewController(
             PanelView view,
             FalsingManager falsingManager,
@@ -247,9 +241,7 @@
             PanelExpansionStateManager panelExpansionStateManager,
             AmbientState ambientState,
             InteractionJankMonitor interactionJankMonitor,
-            KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             SystemClock systemClock) {
-        mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardFadingAwayChanged() {
@@ -261,7 +253,7 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mPanelExpansionStateManager = panelExpansionStateManager;
-        mTouchHandler = createTouchHandler();
+        TouchHandler touchHandler = createTouchHandler();
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -274,7 +266,7 @@
         });
 
         mView.addOnLayoutChangeListener(createLayoutChangeListener());
-        mView.setOnTouchListener(mTouchHandler);
+        mView.setOnTouchListener(touchHandler);
         mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
 
         mResources = mView.getResources();
@@ -398,7 +390,7 @@
     public void startExpandMotion(float newX, float newY, boolean startTracking,
             float expandedHeight) {
         if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
-            beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+            beginJankMonitoring();
         }
         mInitialOffsetOnTouch = expandedHeight;
         mInitialTouchY = newY;
@@ -475,7 +467,7 @@
         } else if (!mCentralSurfaces.isBouncerShowing()
                 && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
                 && !mKeyguardStateController.isKeyguardGoingAway()) {
-            boolean expands = onEmptySpaceClick(mInitialTouchX);
+            boolean expands = onEmptySpaceClick();
             onTrackingStopped(expands);
         }
         mVelocityTracker.clear();
@@ -670,7 +662,7 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 if (!mStatusBarStateController.isDozing()) {
-                    beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+                    beginJankMonitoring();
                 }
             }
 
@@ -702,10 +694,8 @@
         mIsSpringBackAnimation = true;
         ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0);
         animator.addUpdateListener(
-                animation -> {
-                    setOverExpansionInternal((float) animation.getAnimatedValue(),
-                            false /* isFromGesture */);
-                });
+                animation -> setOverExpansionInternal((float) animation.getAnimatedValue(),
+                        false /* isFromGesture */));
         animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION);
         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         animator.addListener(new AnimatorListenerAdapter() {
@@ -731,10 +721,10 @@
         setAnimator(null);
         mKeyguardStateController.notifyPanelFlingEnd();
         if (!cancelled) {
-            endJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+            endJankMonitoring();
             notifyExpandingFinished();
         } else {
-            cancelJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+            cancelJankMonitoring();
         }
         updatePanelExpansionAndVisibility();
     }
@@ -773,13 +763,6 @@
         setExpandedHeight(currentMaxPanelHeight);
     }
 
-    private float getStackHeightFraction(float height) {
-        final float gestureFraction = height / getMaxPanelHeight();
-        final float stackHeightFraction = Interpolators.ACCELERATE_DECELERATE
-                .getInterpolation(gestureFraction);
-        return stackHeightFraction;
-    }
-
     public void setExpandedHeightInternal(float h) {
         if (isNaN(h)) {
             Log.wtf(TAG, "ExpandedHeight set to NaN");
@@ -911,13 +894,8 @@
         return !isFullyCollapsed() && !mTracking && !mClosing;
     }
 
-    private final Runnable mFlingCollapseRunnable = new Runnable() {
-        @Override
-        public void run() {
-            fling(0, false /* expand */, mNextCollapseSpeedUpFactor,
-                    false /* expandBecauseOfFalsing */);
-        }
-    };
+    private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
+            mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
 
     public void expand(final boolean animate) {
         if (!isFullyCollapsed() && !isCollapsing()) {
@@ -950,7 +928,7 @@
                             mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                             if (mAnimateAfterExpanding) {
                                 notifyExpandingStarted();
-                                beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+                                beginJankMonitoring();
                                 fling(0, true /* expand */);
                             } else {
                                 setExpandedFraction(1f);
@@ -1150,7 +1128,7 @@
      *
      * @return whether the panel will be expanded after the action performed by this method
      */
-    protected boolean onEmptySpaceClick(float x) {
+    protected boolean onEmptySpaceClick() {
         if (mHintAnimationRunning) {
             return true;
         }
@@ -1432,9 +1410,9 @@
                     // mHeightAnimator is null, there is no remaining frame, ends instrumenting.
                     if (mHeightAnimator == null) {
                         if (event.getActionMasked() == MotionEvent.ACTION_UP) {
-                            endJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+                            endJankMonitoring();
                         } else {
-                            cancelJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+                            cancelJankMonitoring();
                         }
                     }
                     break;
@@ -1465,28 +1443,32 @@
         }
     }
 
-    private void beginJankMonitoring(int cuj) {
+    private void beginJankMonitoring() {
         if (mInteractionJankMonitor == null) {
             return;
         }
         InteractionJankMonitor.Configuration.Builder builder =
-                InteractionJankMonitor.Configuration.Builder.withView(cuj, mView)
+                InteractionJankMonitor.Configuration.Builder.withView(
+                                InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+                                mView)
                         .setTag(isFullyCollapsed() ? "Expand" : "Collapse");
         mInteractionJankMonitor.begin(builder);
     }
 
-    private void endJankMonitoring(int cuj) {
+    private void endJankMonitoring() {
         if (mInteractionJankMonitor == null) {
             return;
         }
-        InteractionJankMonitor.getInstance().end(cuj);
+        InteractionJankMonitor.getInstance().end(
+                InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
     }
 
-    private void cancelJankMonitoring(int cuj) {
+    private void cancelJankMonitoring() {
         if (mInteractionJankMonitor == null) {
             return;
         }
-        InteractionJankMonitor.getInstance().cancel(cuj);
+        InteractionJankMonitor.getInstance().cancel(
+                InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
     }
 
     protected float getExpansionFraction() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index a2140c6ab..7b8c5fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -423,6 +423,21 @@
                 + getActualPaddingEnd();
     }
 
+    @VisibleForTesting
+    boolean shouldForceOverflow(int i, int speedBumpIndex, float iconAppearAmount,
+            int maxVisibleIcons) {
+        return speedBumpIndex != -1 && i >= speedBumpIndex
+                && iconAppearAmount > 0.0f || i >= maxVisibleIcons;
+    }
+
+    @VisibleForTesting
+    boolean isOverflowing(boolean isLastChild, float translationX, float layoutEnd,
+            float iconSize) {
+        // Layout end, as used here, does not include padding end.
+        final float overflowX = isLastChild ? layoutEnd : layoutEnd - iconSize;
+        return translationX >= overflowX;
+    }
+
     /**
      * Calculate the horizontal translations for each notification based on how much the icons
      * are inserted into the notification container.
@@ -448,26 +463,26 @@
             if (mFirstVisibleIconState == null) {
                 mFirstVisibleIconState = iconState;
             }
-            boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
-                    && iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons;
-            boolean isLastChild = i == childCount - 1;
-            float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
-                    ? ((StatusBarIconView) view).getIconScaleIncreased()
-                    : 1f;
             iconState.visibleState = iconState.hidden
                     ? StatusBarIconView.STATE_HIDDEN
                     : StatusBarIconView.STATE_ICON;
 
-            final float overflowDotX = layoutEnd - mIconSize;
-            boolean isOverflowing = translationX > overflowDotX;
+            final boolean forceOverflow = shouldForceOverflow(i, mSpeedBumpIndex,
+                    iconState.iconAppearAmount, maxVisibleIcons);
+            final boolean isOverflowing = forceOverflow || isOverflowing(
+                    /* isLastChild= */ i == childCount - 1, translationX, layoutEnd, mIconSize);
 
-            if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
-                firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i;
+            // First icon to overflow.
+            if (firstOverflowIndex == -1 && isOverflowing) {
+                firstOverflowIndex = i;
                 mVisualOverflowStart = layoutEnd - mIconSize;
                 if (forceOverflow || mIsStaticLayout) {
                     mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
                 }
             }
+            final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
+                    ? ((StatusBarIconView) view).getIconScaleIncreased()
+                    : 1f;
             translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
         }
         mNumDots = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index fb26600..b94f33a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -501,6 +501,10 @@
         int state = mAnimationScheduler.getAnimationState();
         if (state == IDLE || state == SHOWING_PERSISTENT_DOT) {
             animateShow(mEndSideContent, animate);
+        } else {
+            // We are in the middle of a system status event animation, which will animate the
+            // alpha (but not the visibility). Allow the view to become visible again
+            mEndSideContent.setVisibility(View.VISIBLE);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index d6dfcea..8f127fd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -22,11 +22,12 @@
 import android.os.PowerManager
 import android.provider.Settings
 import androidx.core.view.OneShotPreDrawListener
+import com.android.internal.util.LatencyTracker
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.statusbar.phone.ScreenOffAnimation
 import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.ScreenOffAnimation
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
 import com.android.systemui.util.settings.GlobalSettings
@@ -47,7 +48,8 @@
     private val context: Context,
     private val deviceStateManager: DeviceStateManager,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
-    private val globalSettings: GlobalSettings
+    private val globalSettings: GlobalSettings,
+    private val latencyTracker: LatencyTracker,
 ) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
 
     private lateinit var mCentralSurfaces: CentralSurfaces
@@ -64,12 +66,14 @@
     private var isAnimationPlaying = false
 
     private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
+    private val foldToAodLatencyTracker = FoldToAodLatencyTracker()
 
     private val startAnimationRunnable = Runnable {
-        mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation {
-            // End action
-            setAnimationState(playing = false)
-        }
+        mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation(
+            /* startAction= */ { foldToAodLatencyTracker.onAnimationStarted() },
+            /* endAction= */ { setAnimationState(playing = false) },
+            /* cancelAction= */ { setAnimationState(playing = false) },
+        )
     }
 
     override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
@@ -82,11 +86,13 @@
     /** Returns true if we should run fold to AOD animation */
     override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation
 
-    override fun startAnimation(): Boolean =
-        if (alwaysOnEnabled &&
+    private fun shouldStartAnimation(): Boolean =
+        alwaysOnEnabled &&
             wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
             globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
-        ) {
+
+    override fun startAnimation(): Boolean =
+        if (shouldStartAnimation()) {
             setAnimationState(playing = true)
             mCentralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation()
             true
@@ -97,6 +103,7 @@
 
     override fun onStartedWakingUp() {
         if (isAnimationPlaying) {
+            foldToAodLatencyTracker.cancel()
             handler.removeCallbacks(startAnimationRunnable)
             mCentralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation()
         }
@@ -137,7 +144,8 @@
             // but we should wait for the initial animation preparations to be drawn
             // (setting initial alpha/translation)
             OneShotPreDrawListener.add(
-                mCentralSurfaces.notificationPanelViewController.view, onReady
+                mCentralSurfaces.notificationPanelViewController.view,
+                onReady
             )
         } else {
             // No animation, call ready callback immediately
@@ -209,5 +217,41 @@
                     isFoldHandled = false
                 }
                 this.isFolded = isFolded
-            })
+                if (isFolded) {
+                    foldToAodLatencyTracker.onFolded()
+                }
+            }
+        )
+
+    /**
+     * Tracks the latency of fold to AOD using [LatencyTracker].
+     *
+     * Events that trigger start and end are:
+     *
+     * - Start: Once [DeviceStateManager] sends the folded signal [FoldToAodLatencyTracker.onFolded]
+     * is called and latency tracking starts.
+     * - End: Once the fold -> AOD animation starts, [FoldToAodLatencyTracker.onAnimationStarted] is
+     * called, and latency tracking stops.
+     */
+    private inner class FoldToAodLatencyTracker {
+
+        /** Triggers the latency logging, if needed. */
+        fun onFolded() {
+            if (shouldStartAnimation()) {
+                latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD)
+            }
+        }
+        /**
+         * Called once the Fold -> AOD animation is started.
+         *
+         * For latency tracking, this determines the end of the fold to aod action.
+         */
+        fun onAnimationStarted() {
+            latencyTracker.onActionEnd(LatencyTracker.ACTION_FOLD_TO_AOD)
+        }
+
+        fun cancel() {
+            latencyTracker.onActionCancel(LatencyTracker.ACTION_FOLD_TO_AOD)
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index 6978490..3ac28c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -35,12 +35,12 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.any
 import org.mockito.Mockito.eq
-import org.mockito.Mockito.reset
 import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -63,6 +63,7 @@
         controller = WiredChargingRippleController(
                 commandRegistry, batteryController, configurationController,
                 featureFlags, context, windowManager, systemClock, uiEventLogger)
+        rippleView.setupShader()
         controller.rippleView = rippleView // Replace the real ripple view with a mock instance
         controller.registerCallbacks()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
new file mode 100644
index 0000000..2d2f4cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 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.systemui.ripple
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RippleViewTest : SysuiTestCase() {
+    @Mock
+    private lateinit var rippleView: RippleView
+
+    @Before
+    fun setup() {
+        rippleView = RippleView(context, null)
+    }
+
+    @Test
+    fun testSetupShader_compilesCircle() {
+        rippleView.setupShader(RippleShader.RippleShape.CIRCLE)
+    }
+
+    @Test
+    fun testSetupShader_compilesRoundedBox() {
+        rippleView.setupShader(RippleShader.RippleShape.ROUNDED_BOX)
+    }
+
+    @Test
+    fun testSetupShader_compilesEllipse() {
+        rippleView.setupShader(RippleShader.RippleShape.ELLIPSE)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index c9405c8..3efdf2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -527,7 +527,7 @@
                 mNotificationShadeWindowController,
                 mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
                 mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
-                mMetricsLogger, mActivityManager, mConfigurationController,
+                mMetricsLogger, mConfigurationController,
                 () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
                 mConversationNotificationManager, mMediaHiearchyManager,
                 mStatusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 2ff6dd4..086e5df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -153,6 +153,106 @@
         assertTrue(iconContainer.hasOverflow())
     }
 
+    @Test
+    fun shouldForceOverflow_appearingAboveSpeedBump_true() {
+        val forceOverflow = iconContainer.shouldForceOverflow(
+                /* i= */ 1,
+                /* speedBumpIndex= */ 0,
+                /* iconAppearAmount= */ 1f,
+                /* maxVisibleIcons= */ 5
+        )
+        assertTrue(forceOverflow);
+    }
+
+    @Test
+    fun shouldForceOverflow_moreThanMaxVisible_true() {
+        val forceOverflow = iconContainer.shouldForceOverflow(
+                /* i= */ 10,
+                /* speedBumpIndex= */ 11,
+                /* iconAppearAmount= */ 0f,
+                /* maxVisibleIcons= */ 5
+        )
+        assertTrue(forceOverflow);
+    }
+
+    @Test
+    fun shouldForceOverflow_belowSpeedBumpAndLessThanMaxVisible_false() {
+        val forceOverflow = iconContainer.shouldForceOverflow(
+                /* i= */ 0,
+                /* speedBumpIndex= */ 11,
+                /* iconAppearAmount= */ 0f,
+                /* maxVisibleIcons= */ 5
+        )
+        assertFalse(forceOverflow);
+    }
+
+    @Test
+    fun isOverflowing_lastChildXLessThanLayoutEnd_false() {
+        val isOverflowing = iconContainer.isOverflowing(
+                /* isLastChild= */ true,
+                /* translationX= */ 0f,
+                /* layoutEnd= */ 10f,
+                /* iconSize= */ 2f,
+        )
+        assertFalse(isOverflowing)
+    }
+
+
+    @Test
+    fun isOverflowing_lastChildXEqualToLayoutEnd_true() {
+        val isOverflowing = iconContainer.isOverflowing(
+                /* isLastChild= */ true,
+                /* translationX= */ 10f,
+                /* layoutEnd= */ 10f,
+                /* iconSize= */ 2f,
+        )
+        assertTrue(isOverflowing)
+    }
+
+    @Test
+    fun isOverflowing_lastChildXGreaterThanLayoutEnd_true() {
+        val isOverflowing = iconContainer.isOverflowing(
+                /* isLastChild= */ true,
+                /* translationX= */ 20f,
+                /* layoutEnd= */ 10f,
+                /* iconSize= */ 2f,
+        )
+        assertTrue(isOverflowing)
+    }
+
+    @Test
+    fun isOverflowing_notLastChildXLessThanDotX_false() {
+        val isOverflowing = iconContainer.isOverflowing(
+                /* isLastChild= */ false,
+                /* translationX= */ 0f,
+                /* layoutEnd= */ 10f,
+                /* iconSize= */ 2f,
+        )
+        assertFalse(isOverflowing)
+    }
+
+    @Test
+    fun isOverflowing_notLastChildXGreaterThanDotX_true() {
+        val isOverflowing = iconContainer.isOverflowing(
+                /* isLastChild= */ false,
+                /* translationX= */ 20f,
+                /* layoutEnd= */ 10f,
+                /* iconSize= */ 2f,
+        )
+        assertTrue(isOverflowing)
+    }
+
+    @Test
+    fun isOverflowing_notLastChildXEqualToDotX_true() {
+        val isOverflowing = iconContainer.isOverflowing(
+                /* isLastChild= */ false,
+                /* translationX= */ 8f,
+                /* layoutEnd= */ 10f,
+                /* iconSize= */ 2f,
+        )
+        assertTrue(isOverflowing)
+    }
+
     private fun mockStatusBarIcon() : StatusBarIconView {
         val iconView = mock(StatusBarIconView::class.java)
         whenever(iconView.width).thenReturn(10)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 25348f3..f7a4314 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -16,6 +16,11 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.RUNNING_CHIP_ANIM;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -25,6 +30,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.animation.Animator;
 import android.app.Fragment;
 import android.app.StatusBarManager;
 import android.content.Context;
@@ -132,7 +138,8 @@
     }
 
     @Test
-    public void testDisableSystemInfo() {
+    public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
+        when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
@@ -145,6 +152,98 @@
     }
 
     @Test
+    public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
+        // GIVEN the status bar hides the system info via disable flags, while there is no event
+        CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+        when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
+        fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+
+        // WHEN the disable flags are cleared during a system event animation
+        when(mAnimationScheduler.getAnimationState()).thenReturn(RUNNING_CHIP_ANIM);
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        // THEN the view is made visible again, but still low alpha
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
+
+        // WHEN the system event animation finishes
+        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
+        Animator anim = fragment.onSystemEventAnimationFinish(false);
+        anim.start();
+        processAllMessages();
+        anim.end();
+
+        // THEN the system info is full alpha
+        assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
+    }
+
+    @Test
+    public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
+        // GIVEN the status bar hides the system info via disable flags, while there is no event
+        CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+        when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
+        fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+
+        // WHEN the system event animation finishes
+        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
+        Animator anim = fragment.onSystemEventAnimationFinish(false);
+        anim.start();
+        processAllMessages();
+        anim.end();
+
+        // THEN the system info is at full alpha, but still INVISIBLE (since the disable flag is
+        // still set)
+        assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+    }
+
+
+    @Test
+    public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
+        // GIVEN the status bar is not disabled
+        CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN);
+        // WHEN the system event animation begins
+        Animator anim = fragment.onSystemEventAnimationBegin();
+        anim.start();
+        processAllMessages();
+        anim.end();
+
+        // THEN the system info is visible but alpha 0
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
+    }
+
+    @Test
+    public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
+        // GIVEN the status bar is not disabled
+        CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN);
+        // WHEN the system event animation begins
+        Animator anim = fragment.onSystemEventAnimationBegin();
+        anim.start();
+        processAllMessages();
+        anim.end();
+
+        // THEN the system info is visible but alpha 0
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
+
+        // WHEN the system event animation finishes
+        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
+        anim = fragment.onSystemEventAnimationFinish(false);
+        anim.start();
+        processAllMessages();
+        anim.end();
+
+        // THEN the syste info is full alpha and VISIBLE
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
+    }
+
+    @Test
     public void testDisableNotifications() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
new file mode 100644
index 0000000..f51f783
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2022 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.systemui.unfold
+
+import android.hardware.devicestate.DeviceStateManager
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.os.Handler
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.unfold.util.FoldableDeviceStates
+import com.android.systemui.unfold.util.FoldableTestUtils
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.settings.GlobalSettings
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class FoldAodAnimationControllerTest : SysuiTestCase() {
+
+    @Mock lateinit var deviceStateManager: DeviceStateManager
+
+    @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+
+    @Mock lateinit var globalSettings: GlobalSettings
+
+    @Mock lateinit var latencyTracker: LatencyTracker
+
+    @Mock lateinit var centralSurfaces: CentralSurfaces
+
+    @Mock lateinit var lightRevealScrim: LightRevealScrim
+
+    @Mock lateinit var notificationPanelViewController: NotificationPanelViewController
+
+    @Mock lateinit var viewGroup: ViewGroup
+
+    @Mock lateinit var viewTreeObserver: ViewTreeObserver
+
+    @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+
+    private lateinit var deviceStates: FoldableDeviceStates
+
+    private lateinit var testableLooper: TestableLooper
+
+    lateinit var foldAodAnimationController: FoldAodAnimationController
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+
+        foldAodAnimationController =
+            FoldAodAnimationController(
+                    Handler(testableLooper.looper),
+                    context.mainExecutor,
+                    context,
+                    deviceStateManager,
+                    wakefulnessLifecycle,
+                    globalSettings,
+                    latencyTracker,
+                )
+                .apply { initialize(centralSurfaces, lightRevealScrim) }
+        deviceStates = FoldableTestUtils.findDeviceStates(context)
+
+        whenever(notificationPanelViewController.view).thenReturn(viewGroup)
+        whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
+        whenever(wakefulnessLifecycle.lastSleepReason)
+            .thenReturn(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
+        whenever(centralSurfaces.notificationPanelViewController)
+            .thenReturn(notificationPanelViewController)
+        whenever(notificationPanelViewController.startFoldToAodAnimation(any(), any(), any()))
+            .then {
+                val onActionStarted = it.arguments[0] as Runnable
+                onActionStarted.run()
+            }
+        verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
+
+        foldAodAnimationController.setIsDozing(dozing = true)
+        setAodEnabled(enabled = true)
+        sendFoldEvent(folded = false)
+    }
+
+    @Test
+    fun onFolded_aodDisabled_doesNotLogLatency() {
+        setAodEnabled(enabled = false)
+
+        fold()
+        simulateScreenTurningOn()
+
+        verifyNoMoreInteractions(latencyTracker)
+    }
+
+    @Test
+    fun onFolded_aodEnabled_logsLatency() {
+        setAodEnabled(enabled = true)
+
+        fold()
+        simulateScreenTurningOn()
+
+        verify(latencyTracker).onActionStart(any())
+        verify(latencyTracker).onActionEnd(any())
+    }
+
+    @Test
+    fun onFolded_animationCancelled_doesNotLogLatency() {
+        setAodEnabled(enabled = true)
+
+        fold()
+        foldAodAnimationController.onScreenTurningOn({})
+        foldAodAnimationController.onStartedWakingUp()
+        testableLooper.processAllMessages()
+
+        verify(latencyTracker).onActionStart(any())
+        verify(latencyTracker).onActionCancel(any())
+    }
+
+    private fun simulateScreenTurningOn() {
+        foldAodAnimationController.onScreenTurningOn({})
+        foldAodAnimationController.onScreenTurnedOn()
+        testableLooper.processAllMessages()
+    }
+
+    private fun fold() = sendFoldEvent(folded = true)
+
+    private fun setAodEnabled(enabled: Boolean) =
+        foldAodAnimationController.onAlwaysOnChanged(alwaysOn = enabled)
+
+    private fun sendFoldEvent(folded: Boolean) {
+        val state = if (folded) deviceStates.folded else deviceStates.unfolded
+        foldStateListenerCaptor.value.onStateChanged(state)
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 86b8d32..af15735 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -498,6 +498,29 @@
                 return IKEV2_VPN_RETRY_DELAYS_SEC[retryCount];
             }
         }
+
+        /** Get single threaded executor for IKEv2 VPN */
+        public ScheduledThreadPoolExecutor newScheduledThreadPoolExecutor() {
+            return new ScheduledThreadPoolExecutor(1);
+        }
+
+        /** Get a NetworkAgent instance */
+        public NetworkAgent newNetworkAgent(
+                @NonNull Context context,
+                @NonNull Looper looper,
+                @NonNull String logTag,
+                @NonNull NetworkCapabilities nc,
+                @NonNull LinkProperties lp,
+                @NonNull NetworkScore score,
+                @NonNull NetworkAgentConfig config,
+                @Nullable NetworkProvider provider) {
+            return new NetworkAgent(context, looper, logTag, nc, lp, score, config, provider) {
+                @Override
+                public void onNetworkUnwanted() {
+                    // We are user controlled, not driven by NetworkRequest.
+                }
+            };
+        }
     }
 
     public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd,
@@ -1474,15 +1497,10 @@
                 ? Arrays.asList(mConfig.underlyingNetworks) : null);
 
         mNetworkCapabilities = capsBuilder.build();
-        mNetworkAgent = new NetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
+        mNetworkAgent = mDeps.newNetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
                 mNetworkCapabilities, lp,
                 new NetworkScore.Builder().setLegacyInt(VPN_DEFAULT_SCORE).build(),
-                networkAgentConfig, mNetworkProvider) {
-            @Override
-            public void onNetworkUnwanted() {
-                // We are user controlled, not driven by NetworkRequest.
-            }
-        };
+                networkAgentConfig, mNetworkProvider);
         final long token = Binder.clearCallingIdentity();
         try {
             mNetworkAgent.register();
@@ -2692,11 +2710,10 @@
          * of the mutable Ikev2VpnRunner fields. The Ikev2VpnRunner is built mostly lock-free by
          * virtue of everything being serialized on this executor.
          */
-        @NonNull
-        private final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(1);
+        @NonNull private final ScheduledThreadPoolExecutor mExecutor;
 
-        @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostTimeout;
-        @Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionTimeout;
+        @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostFuture;
+        @Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionFuture;
 
         /** Signal to ensure shutdown is honored even if a new Network is connected. */
         private boolean mIsRunning = true;
@@ -2714,7 +2731,7 @@
         @Nullable private LinkProperties mUnderlyingLinkProperties;
         private final String mSessionKey;
 
-        @Nullable private IkeSession mSession;
+        @Nullable private IkeSessionWrapper mSession;
         @Nullable private IkeSessionConnectionInfo mIkeConnectionInfo;
 
         // mMobikeEnabled can only be updated after IKE AUTH is finished.
@@ -2728,9 +2745,11 @@
          */
         private int mRetryCount = 0;
 
-        IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
+        IkeV2VpnRunner(
+                @NonNull Ikev2VpnProfile profile, @NonNull ScheduledThreadPoolExecutor executor) {
             super(TAG);
             mProfile = profile;
+            mExecutor = executor;
             mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
             mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this, mExecutor);
             mSessionKey = UUID.randomUUID().toString();
@@ -2743,7 +2762,7 @@
 
             // To avoid hitting RejectedExecutionException upon shutdown of the mExecutor */
             mExecutor.setRejectedExecutionHandler(
-                    (r, executor) -> {
+                    (r, exe) -> {
                         Log.d(TAG, "Runnable " + r + " rejected by the mExecutor");
                     });
         }
@@ -2884,7 +2903,6 @@
                     mConfig.dnsServers.addAll(dnsAddrStrings);
 
                     mConfig.underlyingNetworks = new Network[] {network};
-
                     mConfig.disallowedApplications = getAppExclusionList(mPackage);
 
                     networkAgent = mNetworkAgent;
@@ -2900,6 +2918,10 @@
                     } else {
                         // Underlying networks also set in agentConnect()
                         networkAgent.setUnderlyingNetworks(Collections.singletonList(network));
+                        mNetworkCapabilities =
+                                new NetworkCapabilities.Builder(mNetworkCapabilities)
+                                        .setUnderlyingNetworks(Collections.singletonList(network))
+                                        .build();
                     }
 
                     lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
@@ -2933,6 +2955,8 @@
             }
 
             try {
+                mTunnelIface.setUnderlyingNetwork(mIkeConnectionInfo.getNetwork());
+
                 // Transforms do not need to be persisted; the IkeSession will keep
                 // them alive for us
                 mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
@@ -3114,13 +3138,13 @@
             // If the default network is lost during the retry delay, the mActiveNetwork will be
             // null, and the new IKE session won't be established until there is a new default
             // network bringing up.
-            mScheduledHandleRetryIkeSessionTimeout =
+            mScheduledHandleRetryIkeSessionFuture =
                     mExecutor.schedule(() -> {
                         startOrMigrateIkeSession(mActiveNetwork);
 
-                        // Reset mScheduledHandleRetryIkeSessionTimeout since it's already run on
+                        // Reset mScheduledHandleRetryIkeSessionFuture since it's already run on
                         // executor thread.
-                        mScheduledHandleRetryIkeSessionTimeout = null;
+                        mScheduledHandleRetryIkeSessionFuture = null;
                     }, retryDelay, TimeUnit.SECONDS);
         }
 
@@ -3163,12 +3187,10 @@
                 mActiveNetwork = null;
             }
 
-            if (mScheduledHandleNetworkLostTimeout != null
-                    && !mScheduledHandleNetworkLostTimeout.isCancelled()
-                    && !mScheduledHandleNetworkLostTimeout.isDone()) {
+            if (mScheduledHandleNetworkLostFuture != null) {
                 final IllegalStateException exception =
                         new IllegalStateException(
-                                "Found a pending mScheduledHandleNetworkLostTimeout");
+                                "Found a pending mScheduledHandleNetworkLostFuture");
                 Log.i(
                         TAG,
                         "Unexpected error in onDefaultNetworkLost. Tear down session",
@@ -3185,13 +3207,26 @@
                                 + " on session with token "
                                 + mCurrentToken);
 
+                final int token = mCurrentToken;
                 // Delay the teardown in case a new network will be available soon. For example,
                 // during handover between two WiFi networks, Android will disconnect from the
                 // first WiFi and then connects to the second WiFi.
-                mScheduledHandleNetworkLostTimeout =
+                mScheduledHandleNetworkLostFuture =
                         mExecutor.schedule(
                                 () -> {
-                                    handleSessionLost(null, network);
+                                    if (isActiveToken(token)) {
+                                        handleSessionLost(null, network);
+                                    } else {
+                                        Log.d(
+                                                TAG,
+                                                "Scheduled handleSessionLost fired for "
+                                                        + "obsolete token "
+                                                        + token);
+                                    }
+
+                                    // Reset mScheduledHandleNetworkLostFuture since it's
+                                    // already run on executor thread.
+                                    mScheduledHandleNetworkLostFuture = null;
                                 },
                                 NETWORK_LOST_TIMEOUT_MS,
                                 TimeUnit.MILLISECONDS);
@@ -3202,28 +3237,26 @@
         }
 
         private void cancelHandleNetworkLostTimeout() {
-            if (mScheduledHandleNetworkLostTimeout != null
-                    && !mScheduledHandleNetworkLostTimeout.isDone()) {
+            if (mScheduledHandleNetworkLostFuture != null) {
                 // It does not matter what to put in #cancel(boolean), because it is impossible
-                // that the task tracked by mScheduledHandleNetworkLostTimeout is
+                // that the task tracked by mScheduledHandleNetworkLostFuture is
                 // in-progress since both that task and onDefaultNetworkChanged are submitted to
                 // mExecutor who has only one thread.
                 Log.d(TAG, "Cancel the task for handling network lost timeout");
-                mScheduledHandleNetworkLostTimeout.cancel(false /* mayInterruptIfRunning */);
-                mScheduledHandleNetworkLostTimeout = null;
+                mScheduledHandleNetworkLostFuture.cancel(false /* mayInterruptIfRunning */);
+                mScheduledHandleNetworkLostFuture = null;
             }
         }
 
         private void cancelRetryNewIkeSessionFuture() {
-            if (mScheduledHandleRetryIkeSessionTimeout != null
-                    && !mScheduledHandleRetryIkeSessionTimeout.isDone()) {
+            if (mScheduledHandleRetryIkeSessionFuture != null) {
                 // It does not matter what to put in #cancel(boolean), because it is impossible
-                // that the task tracked by mScheduledHandleRetryIkeSessionTimeout is
+                // that the task tracked by mScheduledHandleRetryIkeSessionFuture is
                 // in-progress since both that task and onDefaultNetworkChanged are submitted to
                 // mExecutor who has only one thread.
                 Log.d(TAG, "Cancel the task for handling new ike session timeout");
-                mScheduledHandleRetryIkeSessionTimeout.cancel(false /* mayInterruptIfRunning */);
-                mScheduledHandleRetryIkeSessionTimeout = null;
+                mScheduledHandleRetryIkeSessionFuture.cancel(false /* mayInterruptIfRunning */);
+                mScheduledHandleRetryIkeSessionFuture = null;
             }
         }
 
@@ -3263,7 +3296,7 @@
         }
 
         private void handleSessionLost(@Nullable Exception exception, @Nullable Network network) {
-            // Cancel mScheduledHandleNetworkLostTimeout if the session it is going to terminate is
+            // Cancel mScheduledHandleNetworkLostFuture if the session it is going to terminate is
             // already terminated due to other failures.
             cancelHandleNetworkLostTimeout();
 
@@ -4015,7 +4048,9 @@
                 case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
                 case VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS:
                     mVpnRunner =
-                            new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
+                            new IkeV2VpnRunner(
+                                    Ikev2VpnProfile.fromVpnProfile(profile),
+                                    mDeps.newScheduledThreadPoolExecutor());
                     mVpnRunner.start();
                     break;
                 default:
@@ -4191,22 +4226,48 @@
      * @hide
      */
     @VisibleForTesting
+    public static class IkeSessionWrapper {
+        private final IkeSession mImpl;
+
+        /** Create an IkeSessionWrapper */
+        public IkeSessionWrapper(IkeSession session) {
+            mImpl = session;
+        }
+
+        /** Update the underlying network of the IKE Session */
+        public void setNetwork(@NonNull Network network) {
+            mImpl.setNetwork(network);
+        }
+
+        /** Forcibly terminate the IKE Session */
+        public void kill() {
+            mImpl.kill();
+        }
+    }
+
+    /**
+     * Proxy to allow testing
+     *
+     * @hide
+     */
+    @VisibleForTesting
     public static class Ikev2SessionCreator {
         /** Creates a IKE session */
-        public IkeSession createIkeSession(
+        public IkeSessionWrapper createIkeSession(
                 @NonNull Context context,
                 @NonNull IkeSessionParams ikeSessionParams,
                 @NonNull ChildSessionParams firstChildSessionParams,
                 @NonNull Executor userCbExecutor,
                 @NonNull IkeSessionCallback ikeSessionCallback,
                 @NonNull ChildSessionCallback firstChildSessionCallback) {
-            return new IkeSession(
-                    context,
-                    ikeSessionParams,
-                    firstChildSessionParams,
-                    userCbExecutor,
-                    ikeSessionCallback,
-                    firstChildSessionCallback);
+            return new IkeSessionWrapper(
+                    new IkeSession(
+                            context,
+                            ikeSessionParams,
+                            firstChildSessionParams,
+                            userCbExecutor,
+                            ikeSessionCallback,
+                            firstChildSessionCallback));
         }
     }
 
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 6fd8841..72464be 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -344,7 +344,9 @@
             brightnessEvent.recommendedBrightness = mScreenAutoBrightness;
             brightnessEvent.flags |= (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0)
                     | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
-                        ? BrightnessEvent.FLAG_DOZE_SCALE : 0);
+                        ? BrightnessEvent.FLAG_DOZE_SCALE : 0)
+                    | (mCurrentBrightnessMapper.isForIdleMode()
+                        ? BrightnessEvent.FLAG_IDLE_CURVE : 0);
         }
 
         if (!mAmbientLuxValid) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 62b1cfe..8781a8d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2738,8 +2738,9 @@
     class BrightnessEvent {
         static final int FLAG_RBC = 0x1;
         static final int FLAG_INVALID_LUX = 0x2;
-        static final int FLAG_DOZE_SCALE = 0x3;
-        static final int FLAG_USER_SET = 0x4;
+        static final int FLAG_DOZE_SCALE = 0x4;
+        static final int FLAG_USER_SET = 0x8;
+        static final int FLAG_IDLE_CURVE = 0x16;
 
         public final BrightnessReason reason = new BrightnessReason();
 
@@ -2843,7 +2844,8 @@
             return ((flags & FLAG_USER_SET) != 0 ? "user_set " : "")
                     + ((flags & FLAG_RBC) != 0 ? "rbc " : "")
                     + ((flags & FLAG_INVALID_LUX) != 0 ? "invalid_lux " : "")
-                    + ((flags & FLAG_DOZE_SCALE) != 0 ? "doze_scale " : "");
+                    + ((flags & FLAG_DOZE_SCALE) != 0 ? "doze_scale " : "")
+                    + ((flags & FLAG_DOZE_SCALE) != 0 ? "idle_curve " : "");
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1f3f039..7a7041d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4746,9 +4746,6 @@
             mPendingRemoteAnimation = options.getRemoteAnimationAdapter();
         }
         mPendingRemoteTransition = options.getRemoteTransition();
-        // Since options gets sent to client apps, remove transition information from it.
-        options.setRemoteTransition(null);
-        options.setRemoteAnimationAdapter(null);
     }
 
     void applyOptionsAnimation() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index c8fcee6..2591a36 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2568,6 +2568,9 @@
                     }
                     task = r.getTask();
                 }
+                // If {@code isSystemCaller} is {@code true}, it means the user intends to stop
+                // pinned mode through UI; otherwise, it's called by an app and we need to stop
+                // locked or pinned mode, subject to checks.
                 getLockTaskController().stopLockTaskMode(task, isSystemCaller, callingUid);
             }
             // Launch in-call UI if a call is ongoing. This is necessary to allow stopping the lock
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7aa0541..25d187f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1900,8 +1900,10 @@
     /**
      * Called when the resource overlays change.
      */
-    public void onOverlayChangedLw() {
+    void onOverlayChanged() {
         updateCurrentUserResources();
+        // Update the latest display size, cutout.
+        mDisplayContent.updateDisplayInfo();
         onConfigurationChanged();
         mSystemGestures.onConfigurationChanged();
     }
@@ -2933,7 +2935,10 @@
             return;
         }
 
-        mDisplayContent.unregisterPointerEventListener(mPointerLocationView);
+        if (!mDisplayContent.isRemoved()) {
+            mDisplayContent.unregisterPointerEventListener(mPointerLocationView);
+        }
+
         final WindowManager wm = mContext.getSystemService(WindowManager.class);
         wm.removeView(mPointerLocationView);
         mPointerLocationView = null;
@@ -2958,6 +2963,9 @@
         mHandler.post(mGestureNavigationSettingsObserver::unregister);
         mHandler.post(mForceShowNavBarSettingsObserver::unregister);
         mImmersiveModeConfirmation.release();
+        if (mService.mPointerLocationEnabled) {
+            setPointerLocationEnabled(false);
+        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 7a055d2..f11c2a7 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -481,27 +481,24 @@
      *
      * @param task the task that requested the end of lock task mode ({@code null} for quitting app
      *             pinning mode)
-     * @param isSystemCaller indicates whether this request comes from the system via
-     *                       {@link ActivityTaskManagerService#stopSystemLockTaskMode()}. If
-     *                       {@code true}, it means the user intends to stop pinned mode through UI;
-     *                       otherwise, it's called by an app and we need to stop locked or pinned
-     *                       mode, subject to checks.
+     * @param stopAppPinning indicates whether to stop app pinning mode or to stop a task from
+     *                       being locked.
      * @param callingUid the caller that requested the end of lock task mode.
      * @throws IllegalArgumentException if the calling task is invalid (e.g., {@code null} or not in
      *                                  foreground)
      * @throws SecurityException if the caller is not authorized to stop the lock task mode, i.e. if
      *                           they differ from the one that launched lock task mode.
      */
-    void stopLockTaskMode(@Nullable Task task, boolean isSystemCaller, int callingUid) {
+    void stopLockTaskMode(@Nullable Task task, boolean stopAppPinning, int callingUid) {
         if (mLockTaskModeState == LOCK_TASK_MODE_NONE) {
             return;
         }
 
-        if (isSystemCaller) {
+        if (stopAppPinning) {
             if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
                 clearLockedTasks("stopAppPinning");
             } else {
-                Slog.e(TAG_LOCKTASK, "Attempted to stop LockTask with isSystemCaller=true");
+                Slog.e(TAG_LOCKTASK, "Attempted to stop app pinning while fully locked");
                 showLockTaskToast();
             }
 
@@ -642,6 +639,10 @@
      * @param callingUid the caller that requested the launch of lock task mode.
      */
     void startLockTaskMode(@NonNull Task task, boolean isSystemCaller, int callingUid) {
+        if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
+            ProtoLog.w(WM_DEBUG_LOCKTASK, "startLockTaskMode: Can't lock due to auth");
+            return;
+        }
         if (!isSystemCaller) {
             task.mLockTaskUid = callingUid;
             if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
@@ -654,6 +655,11 @@
                     statusBarManager.showScreenPinningRequest(task.mTaskId);
                 }
                 return;
+            } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+                // startLockTask() called by app, and app is part of lock task allowlist.
+                // Deactivate the currently pinned task before doing so.
+                Slog.i(TAG, "Stop app pinning before entering full lock task mode");
+                stopLockTaskMode(/* task= */ null, /* stopAppPinning= */ true, callingUid);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 08c4732..296390a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7046,13 +7046,14 @@
     }
 
     public void onOverlayChanged() {
-        synchronized (mGlobalLock) {
-            mRoot.forAllDisplays(displayContent -> {
-                displayContent.getDisplayPolicy().onOverlayChangedLw();
-                displayContent.updateDisplayInfo();
-            });
-            requestTraversal();
-        }
+        // Post to display thread so it can get the latest display info.
+        mH.post(() -> {
+            synchronized (mGlobalLock) {
+                mAtmService.deferWindowLayout();
+                mRoot.forAllDisplays(dc -> dc.getDisplayPolicy().onOverlayChanged());
+                mAtmService.continueWindowLayout();
+            }
+        });
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 86e14c2..86fa356 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2453,8 +2453,7 @@
             dc.setImeLayeringTarget(null);
             dc.computeImeTarget(true /* updateImeTarget */);
         }
-        if (dc.getImeInputTarget() == this
-                && (mActivityRecord == null || !mActivityRecord.isRelaunching())) {
+        if (dc.getImeInputTarget() == this && !inRelaunchingActivity()) {
             dc.updateImeInputAndControlTarget(null);
         }
 
@@ -2581,7 +2580,10 @@
                 // usually unnoticeable (e.g. covered by rotation animation) and the animation
                 // bounds could be inconsistent, such as depending on when the window applies
                 // its draw transaction with new rotation.
-                final boolean allowExitAnimation = !getDisplayContent().inTransition();
+                final boolean allowExitAnimation = !getDisplayContent().inTransition()
+                        // There will be a new window so the exit animation may not be visible or
+                        // look weird if its orientation is changed.
+                        && !inRelaunchingActivity();
 
                 if (wasVisible) {
                     final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
@@ -3871,7 +3873,7 @@
         // If the activity is scheduled to relaunch, skip sending the resized to ViewRootImpl now
         // since it will be destroyed anyway. This also prevents the client from receiving
         // windowing mode change before it is destroyed.
-        if (mActivityRecord != null && mActivityRecord.isRelaunching()) {
+        if (inRelaunchingActivity()) {
             return;
         }
         // If this is an activity or wallpaper and is invisible or going invisible, don't report
@@ -3957,6 +3959,10 @@
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
+    boolean inRelaunchingActivity() {
+        return mActivityRecord != null && mActivityRecord.isRelaunching();
+    }
+
     boolean isClientLocal() {
         return mClient instanceof IWindow.Stub;
     }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index bbb21f8..72e7e65 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -46,6 +46,7 @@
 import android.view.InsetsState;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams.WindowType;
 import android.window.WindowContext;
 
@@ -558,6 +559,12 @@
             // The window may be detached or detaching.
             return;
         }
+        if (mTransitionController.isShellTransitionsEnabled()
+                && asActivityRecord() != null && isVisible()) {
+            // Trigger an activity level rotation transition.
+            mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_CHANGE, this);
+            mTransitionController.setReady(this);
+        }
         final int originalRotation = getWindowConfiguration().getRotation();
         onConfigurationChanged(parent.getConfiguration());
         onCancelFixedRotationTransform(originalRotation);
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 4469ffc..7eec86a 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -115,15 +115,15 @@
         /** @hide */
         public static @RadioAccessNetworkType int fromString(@NonNull String str) {
             switch (str.toUpperCase()) {
-                case "GERAN" : return GERAN;
-                case "UTRAN" : return UTRAN;
-                case "EUTRAN" : return EUTRAN;
-                case "CDMA2000" : return CDMA2000;
-                case "IWLAN" : return IWLAN;
-                case "NGRAN" : return NGRAN;
+                case "UNKNOWN": return UNKNOWN;
+                case "GERAN": return GERAN;
+                case "UTRAN": return UTRAN;
+                case "EUTRAN": return EUTRAN;
+                case "CDMA2000": return CDMA2000;
+                case "IWLAN": return IWLAN;
+                case "NGRAN": return NGRAN;
                 default:
-                    Rlog.e(TAG, "Invalid access network type " + str);
-                    return UNKNOWN;
+                    throw new IllegalArgumentException("Invalid access network type " + str);
             }
         }
     }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 70fe6b1..e032f65 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8550,6 +8550,13 @@
      * IWLAN handover rules that determine whether handover is allowed or disallowed between
      * cellular and IWLAN.
      *
+     * Rule syntax: "source=[GERAN|UTRAN|EUTRAN|NGRAN|IWLAN|UNKNOWN], target=[GERAN|UTRAN|EUTRAN
+     * |NGRAN|IWLAN], type=[allowed|disallowed], roaming=[true|false], capabilities=[INTERNET|MMS
+     * |FOTA|IMS|CBS|SUPL|EIMS|XCAP|DUN]"
+     *
+     * Note that UNKNOWN can be only specified in the source access network and can be only used
+     * in the disallowed rule.
+     *
      * The handover rules will be matched in the order. Here are some sample rules.
      * <string-array name="iwlan_handover_rules" num="5">
      *     <!-- Handover from IWLAN to 2G/3G is not allowed -->