Merge "Add the outline back for off switches." into main
diff --git a/core/java/android/app/admin/PolicyUpdateReceiver.java b/core/java/android/app/admin/PolicyUpdateReceiver.java
index be13988..630ab0e 100644
--- a/core/java/android/app/admin/PolicyUpdateReceiver.java
+++ b/core/java/android/app/admin/PolicyUpdateReceiver.java
@@ -20,10 +20,12 @@
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.util.Objects;
@@ -46,6 +48,10 @@
 public abstract class PolicyUpdateReceiver extends BroadcastReceiver {
     private static String TAG = "PolicyUpdateReceiver";
 
+    //TODO(b/378931989): Switch to android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY
+    //when the appropriate flag is launched.
+    private static final String MEMORY_TAGGING_POLICY = "memoryTagging";
+
     /**
      * Action for a broadcast sent to admins to communicate back the result of setting a policy in
      * {@link DevicePolicyManager}.
@@ -156,15 +162,28 @@
     @Override
     public final void onReceive(Context context, Intent intent) {
         Objects.requireNonNull(intent.getAction());
+        String policyKey;
         switch (intent.getAction()) {
             case ACTION_DEVICE_POLICY_SET_RESULT:
                 Log.i(TAG, "Received ACTION_DEVICE_POLICY_SET_RESULT");
-                onPolicySetResult(context, getPolicyKey(intent), getPolicyExtraBundle(intent),
+                policyKey = getPolicyKey(intent);
+                if (!shouldPropagatePolicy(policyKey)) {
+                    Log.d(TAG, TextUtils.formatSimple(
+                            "Skipping propagation of policy %s", policyKey));
+                    break;
+                }
+                onPolicySetResult(context, policyKey, getPolicyExtraBundle(intent),
                         getTargetUser(intent), getPolicyChangedReason(intent));
                 break;
             case ACTION_DEVICE_POLICY_CHANGED:
                 Log.i(TAG, "Received ACTION_DEVICE_POLICY_CHANGED");
-                onPolicyChanged(context, getPolicyKey(intent), getPolicyExtraBundle(intent),
+                policyKey = getPolicyKey(intent);
+                if (!shouldPropagatePolicy(policyKey)) {
+                    Log.d(TAG, TextUtils.formatSimple(
+                            "Skipping propagation of policy %s", policyKey));
+                    break;
+                }
+                onPolicyChanged(context, policyKey, getPolicyExtraBundle(intent),
                         getTargetUser(intent), getPolicyChangedReason(intent));
                 break;
             default:
@@ -217,6 +236,14 @@
         return new TargetUser(targetUserId);
     }
 
+    /**
+     * @hide
+     */
+    private boolean shouldPropagatePolicy(String policyKey) {
+        return !MEMORY_TAGGING_POLICY.equals(policyKey) || Flags.setMtePolicyCoexistence();
+    }
+
+
     // TODO(b/260847505): Add javadocs to explain which DPM APIs are supported
     /**
      * Callback triggered after an admin has set a policy using one of the APIs in
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
index 0270edf..8217ca1 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -97,7 +97,7 @@
      *
      * @hide
      */
-    @RequiresPermission(value = android.Manifest.permission.QUERY_USERS)
+    @RequiresPermission(anyOf = {MANAGE_USERS, QUERY_USERS})
     @Nullable
     public Intent createConfirmSupervisionCredentialsIntent() {
         if (mService != null) {
diff --git a/core/java/android/content/pm/parsing/OWNERS b/core/java/android/content/pm/parsing/OWNERS
index 445a833..b8fa1a9 100644
--- a/core/java/android/content/pm/parsing/OWNERS
+++ b/core/java/android/content/pm/parsing/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 36137
 
-chiuwinson@google.com
 patb@google.com
diff --git a/core/java/android/gesture/OWNERS b/core/java/android/gesture/OWNERS
index 168630a..ffa753a 100644
--- a/core/java/android/gesture/OWNERS
+++ b/core/java/android/gesture/OWNERS
@@ -3,5 +3,4 @@
 romainguy@google.com
 adamp@google.com
 aurimas@google.com
-nduca@google.com
 sumir@google.com
diff --git a/core/java/android/metrics/OWNERS b/core/java/android/metrics/OWNERS
index ba867e0..98aaf3f 100644
--- a/core/java/android/metrics/OWNERS
+++ b/core/java/android/metrics/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 26805
 
 cwren@android.com
-cwren@google.com
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java
index 2848bcb..8a3a5be 100644
--- a/core/java/android/os/PerfettoTrackEventExtra.java
+++ b/core/java/android/os/PerfettoTrackEventExtra.java
@@ -214,9 +214,6 @@
          * Initialize the builder for a new trace event.
          */
         public Builder init(int traceType, PerfettoTrace.Category category) {
-            if (!category.isEnabled()) {
-                return this;
-            }
             mTraceType = traceType;
             mCategory = category;
             mEventName = "";
@@ -228,7 +225,7 @@
 
             mExtra.reset();
             // Reset after on init in case the thread created builders without calling emit
-            return initInternal(this, null, true);
+            return initInternal(this, null, category.isEnabled());
         }
 
         /**
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 25e8a4d..3fd9418 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -749,6 +749,10 @@
             getListView().clearChoices();
         } else if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) {
             super.onBackPressed();
+        } else if (!mIsBackCallbackRegistered) {
+            // If predictive back is enabled and no callback is registered, finish the activity.
+            // This ensures correct back navigation behaviour when onBackPressed is called manually.
+            finish();
         }
         updateBackCallbackRegistrationState();
     }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 89f66c0..1210790 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9357,6 +9357,16 @@
                 "accessibility_autoclick_panel_position";
 
         /**
+         * Setting that specifies whether autoclick type reverts to left click after performing
+         * an action when {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set.
+         *
+         * @see #ACCESSIBILITY_AUTOCLICK_ENABLED
+         * @hide
+         */
+        public static final String ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK =
+                "accessibility_autoclick_revert_to_left_click";
+
+        /**
          * Whether or not larger size icons are used for the pointer of mouse/trackpad for
          * accessibility.
          * (0 = false, 1 = true)
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
index ea01fc9..770e234 100644
--- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
@@ -16,7 +16,6 @@
 
 package android.security.advancedprotection;
 
-import static android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.UserManager.DISALLOW_CELLULAR_2G;
 import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY;
@@ -59,6 +58,10 @@
 public final class AdvancedProtectionManager {
     private static final String TAG = "AdvancedProtectionMgr";
 
+    //TODO(b/378931989): Switch to android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY
+    //when the appropriate flag is launched.
+    private static final String MEMORY_TAGGING_POLICY = "memoryTagging";
+
     /**
      * Advanced Protection's identifier for setting policies or restrictions in
      * {@link DevicePolicyManager}.
@@ -359,8 +362,7 @@
             featureId = FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES;
         } else if (DISALLOW_CELLULAR_2G.equals(identifier)) {
             featureId = FEATURE_ID_DISALLOW_CELLULAR_2G;
-        } else if (android.app.admin.flags.Flags.setMtePolicyCoexistence() && MEMORY_TAGGING_POLICY
-                .equals(identifier)) {
+        } else if (MEMORY_TAGGING_POLICY.equals(identifier)) {
             featureId = FEATURE_ID_ENABLE_MTE;
         } else {
             throw new UnsupportedOperationException("Unsupported identifier: " + identifier);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 41a64e2..744cdf6 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1323,14 +1323,13 @@
                                 redrawNeeded ? 1 : 0));
                         return;
                     }
-
-                    final int transformHint = SurfaceControl.rotationToBufferTransform(
-                            (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);
-                    mSurfaceControl.setTransformHint(transformHint);
                     WindowLayout.computeSurfaceSize(mLayout, maxBounds, mWidth, mHeight,
                             mWinFrames.frame, false /* dragResizing */, mSurfaceSize);
 
                     if (mSurfaceControl.isValid()) {
+                        final int transformHint = SurfaceControl.rotationToBufferTransform(
+                            (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);
+                        mSurfaceControl.setTransformHint(transformHint);
                         if (mBbqSurfaceControl == null) {
                             mBbqSurfaceControl = new SurfaceControl.Builder()
                                     .setName("Wallpaper BBQ wrapper")
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index 19e0913..c39456a 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -16,7 +16,11 @@
 
 package android.view;
 
+import static android.view.InsetsController.ANIMATION_DURATION_SYNC_IME_MS;
+import static android.view.InsetsController.ANIMATION_DURATION_UNSYNC_IME_MS;
 import static android.view.InsetsController.ANIMATION_TYPE_USER;
+import static android.view.InsetsController.FAST_OUT_LINEAR_IN_INTERPOLATOR;
+import static android.view.InsetsController.SYNC_IME_INTERPOLATOR;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
@@ -56,8 +60,6 @@
     private static final Interpolator BACK_GESTURE = new BackGestureInterpolator();
     private static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
             0.05f, 0.7f, 0.1f, 1f);
-    private static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(0.3f, 0f, 1f, 1f);
-
     private final InsetsController mInsetsController;
     private final ViewRootImpl mViewRoot;
     private WindowInsetsAnimationController mWindowInsetsAnimationController = null;
@@ -183,8 +185,21 @@
         float targetProgress = triggerBack ? 1f : 0f;
         mPostCommitAnimator = ValueAnimator.ofFloat(
                 BACK_GESTURE.getInterpolation(mLastProgress) * PEEK_FRACTION, targetProgress);
-        mPostCommitAnimator.setInterpolator(
-                triggerBack ? STANDARD_ACCELERATE : EMPHASIZED_DECELERATE);
+        Interpolator interpolator;
+        long duration;
+        if (triggerBack && mViewRoot.mView.hasWindowInsetsAnimationCallback()
+                && mWindowInsetsAnimationController.getShownStateInsets().bottom != 0) {
+            interpolator = SYNC_IME_INTERPOLATOR;
+            duration = ANIMATION_DURATION_SYNC_IME_MS;
+        } else if (triggerBack) {
+            interpolator = FAST_OUT_LINEAR_IN_INTERPOLATOR;
+            duration = ANIMATION_DURATION_UNSYNC_IME_MS;
+        } else {
+            interpolator = EMPHASIZED_DECELERATE;
+            duration = POST_COMMIT_CANCEL_DURATION_MS;
+        }
+        mPostCommitAnimator.setInterpolator(interpolator);
+        mPostCommitAnimator.setDuration(duration);
         mPostCommitAnimator.addUpdateListener(animation -> {
             if (mWindowInsetsAnimationController != null) {
                 setInterpolatedProgress((float) animation.getAnimatedValue());
@@ -207,8 +222,6 @@
                 reset();
             }
         });
-        mPostCommitAnimator.setDuration(
-                triggerBack ? POST_COMMIT_DURATION_MS : POST_COMMIT_CANCEL_DURATION_MS);
         mPostCommitAnimator.start();
         if (triggerBack) {
             mInsetsController.setPredictiveBackImeHideAnimInProgress(true);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 462c5c6..6f346bd 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -235,8 +235,8 @@
 
     private static final int ANIMATION_DELAY_DIM_MS = 500;
 
-    private static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
-    private static final int ANIMATION_DURATION_UNSYNC_IME_MS = 200;
+    static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
+    static final int ANIMATION_DURATION_UNSYNC_IME_MS = 200;
 
     private static final int PENDING_CONTROL_TIMEOUT_MS = 2000;
 
@@ -256,11 +256,11 @@
             return 1f - SYSTEM_BARS_ALPHA_INTERPOLATOR.getInterpolation(innerFraction);
         }
     };
-    private static final Interpolator SYNC_IME_INTERPOLATOR =
+    static final Interpolator SYNC_IME_INTERPOLATOR =
             new PathInterpolator(0.2f, 0f, 0f, 1f);
     private static final Interpolator LINEAR_OUT_SLOW_IN_INTERPOLATOR =
             new PathInterpolator(0, 0, 0.2f, 1f);
-    private static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
+    static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
             new PathInterpolator(0.4f, 0f, 1f, 1f);
 
     /** Visible for WindowManagerWrapper */
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index 80484a6..3353923 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -3,7 +3,6 @@
 romainguy@google.com
 adamp@google.com
 aurimas@google.com
-nduca@google.com
 sumir@google.com
 ogunwale@google.com
 jjaggi@google.com
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e157da7..9d0773f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2071,26 +2071,40 @@
      */
     @VisibleForTesting
     public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() {
-        if (forceInvertColor()) {
-            // Force invert ignores all developer opt-outs.
-            // We also ignore dark theme, since the app developer can override the user's preference
-            // for dark mode in configuration.uiMode. Instead, we assume that both force invert and
-            // the system's dark theme are enabled.
-            if (getUiModeManager().getForceInvertState() == UiModeManager.FORCE_INVERT_TYPE_DARK) {
-                return ForceDarkType.FORCE_INVERT_COLOR_DARK;
+        TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
+        try {
+            if (forceInvertColor()) {
+                // Force invert ignores all developer opt-outs.
+                // We also ignore dark theme, since the app developer can override the user's
+                // preference for dark mode in configuration.uiMode. Instead, we assume that both
+                // force invert and the system's dark theme are enabled.
+                if (getUiModeManager().getForceInvertState() ==
+                        UiModeManager.FORCE_INVERT_TYPE_DARK) {
+                    final boolean isLightTheme =
+                        a.getBoolean(R.styleable.Theme_isLightTheme, false);
+                    // TODO: b/372558459 - Also check the background ColorDrawable color lightness
+                    // TODO: b/368725782 - Use hwui color area detection instead of / in
+                    //  addition to these heuristics.
+                    if (isLightTheme) {
+                        return ForceDarkType.FORCE_INVERT_COLOR_DARK;
+                    } else {
+                        return ForceDarkType.NONE;
+                    }
+                }
             }
-        }
 
-        boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
-        if (useAutoDark) {
-            boolean forceDarkAllowedDefault =
-                    SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
-            TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
-            useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
-                    && a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);
+            boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
+            if (useAutoDark) {
+                boolean forceDarkAllowedDefault =
+                        SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
+                useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
+                        && a.getBoolean(R.styleable.Theme_forceDarkAllowed,
+                            forceDarkAllowedDefault);
+            }
+            return useAutoDark ? ForceDarkType.FORCE_DARK : ForceDarkType.NONE;
+        } finally {
             a.recycle();
         }
-        return useAutoDark ? ForceDarkType.FORCE_DARK : ForceDarkType.NONE;
     }
 
     private void updateForceDarkMode() {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index d267c94..e43fb48 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -163,6 +163,9 @@
     /** @hide */
     public static final boolean AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT = false;
 
+    /** @hide */
+    public static final boolean AUTOCLICK_REVERT_TO_LEFT_CLICK_DEFAULT = true;
+
     /**
      * Activity action: Launch UI to manage which accessibility service or feature is assigned
      * to the navigation bar Accessibility button.
diff --git a/core/java/android/view/inspector/OWNERS b/core/java/android/view/inspector/OWNERS
index 705f4b3..f345034 100644
--- a/core/java/android/view/inspector/OWNERS
+++ b/core/java/android/view/inspector/OWNERS
@@ -2,5 +2,4 @@
 alanv@google.com
 adamp@google.com
 aurimas@google.com
-nduca@google.com
 sumir@google.com
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 1baf3b8..c5e558f 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -531,8 +531,9 @@
      * the chosen file(s). WebView can access all files that your app can access.
      * In case the file(s) are chosen through an untrusted source such as a third-party
      * app, it is your own app's responsibility to check what the returned Uris
-     * refer to before calling the <code>filePathCallback</code>. See
-     * {@link #createIntent} and {@link #parseResult} for more details.</p>
+     * refer to before calling the {@code filePathCallback}. See
+     * {@link FileChooserParams#createIntent} and {@link FileChooserParams#parseResult} for more
+     * details.
      *
      * @param webView The WebView instance that is initiating the request.
      * @param filePathCallback Invoke this callback to supply the list of paths to files to upload,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5e828ba..99fe0cb 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5211,11 +5211,7 @@
      */
     @Nullable
     public String getFontVariationSettings() {
-        if (Flags.typefaceRedesignReadonly()) {
-            return mTextPaint.getFontVariationOverride();
-        } else {
-            return mTextPaint.getFontVariationSettings();
-        }
+        return mTextPaint.getFontVariationSettings();
     }
 
     /**
@@ -5571,10 +5567,10 @@
                             Math.clamp(400 + mFontWeightAdjustment,
                                     FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX)));
                 }
-                mTextPaint.setFontVariationOverride(
+                mTextPaint.setFontVariationSettings(
                         FontVariationAxis.toFontVariationSettings(axes));
             } else {
-                mTextPaint.setFontVariationOverride(fontVariationSettings);
+                mTextPaint.setFontVariationSettings(fontVariationSettings);
             }
             effective = true;
         } else {
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 696b7b8..1b8f73a 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -126,6 +126,8 @@
     ENABLE_TILE_RESIZING(Flags::enableTileResizing, true),
     ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING(Flags::enableTopVisibleRootTaskPerUserTracking,
             true),
+    ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX(
+            Flags::enableVisualIndicatorInTransitionBugfix, false),
     ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
     ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
     ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true),
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 63c55ad..be4edc3 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -29,6 +29,7 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_NONE;
@@ -375,7 +376,8 @@
      */
     public boolean hasChangesOrSideEffects() {
         return !mChanges.isEmpty() || isKeyguardGoingAway()
-                || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0;
+                || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0
+                || (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0;
     }
 
     /**
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 09f458b..2f2a09a 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -864,4 +864,14 @@
     metadata {
         purpose: PURPOSE_BUGFIX
     }
-}
\ No newline at end of file
+}
+
+flag {
+    name: "enable_visual_indicator_in_transition_bugfix"
+    namespace: "lse_desktop_experience"
+    description: "Enables bugfix to move visual drop-zone indicator to transition root, so it can't be shown after the transition."
+    bug: "392826275"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index 6498c53..a2d6c2b 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -215,7 +215,6 @@
                         Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
         targets.add(colorInversion);
 
-        // TODO(b/394683600): Update Icon with the autoclick asset.
         final ToggleAllowListingFeatureTarget autoclick =
                 new ToggleAllowListingFeatureTarget(context,
                         shortcutType,
@@ -224,7 +223,7 @@
                         AUTOCLICK_COMPONENT_NAME.flattenToString(),
                         uid,
                         context.getString(R.string.autoclick_feature_name),
-                        context.getDrawable(R.drawable.ic_accessibility_generic),
+                        context.getDrawable(R.drawable.ic_accessibility_autoclick),
                         Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
         targets.add(autoclick);
 
diff --git a/core/java/com/android/internal/config/appcloning/OWNERS b/core/java/com/android/internal/config/appcloning/OWNERS
index 0645a8c5..9369438 100644
--- a/core/java/com/android/internal/config/appcloning/OWNERS
+++ b/core/java/com/android/internal/config/appcloning/OWNERS
@@ -1,3 +1,2 @@
 # Bug component: 1207885
 jigarthakkar@google.com
-saumyap@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/remotecompose/OWNERS b/core/java/com/android/internal/widget/remotecompose/OWNERS
index e163474..c606744 100644
--- a/core/java/com/android/internal/widget/remotecompose/OWNERS
+++ b/core/java/com/android/internal/widget/remotecompose/OWNERS
@@ -1,6 +1,5 @@
 nicolasroard@google.com
 hoford@google.com
-jnichol@google.com
 sihua@google.com
 sunnygoyal@google.com
 oscarad@google.com
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index b8503da..796ea8d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -65,7 +65,7 @@
 
     // We also keep a more fine-grained BUILD number, exposed as
     // ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD
-    static final float BUILD = 0.2f;
+    static final float BUILD = 0.3f;
 
     @NonNull ArrayList<Operation> mOperations = new ArrayList<>();
 
@@ -411,7 +411,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", "CoreDocument")
+                .addType("CoreDocument")
                 .add("width", mWidth)
                 .add("height", mHeight)
                 .add("operations", mOperations);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index 09ec402..9cbafab 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -100,6 +100,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.FitBoxLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout;
@@ -231,6 +232,7 @@
     public static final int LAYOUT_ROOT = 200;
     public static final int LAYOUT_CONTENT = 201;
     public static final int LAYOUT_BOX = 202;
+    public static final int LAYOUT_FIT_BOX = 176;
     public static final int LAYOUT_ROW = 203;
     public static final int LAYOUT_COLLAPSIBLE_ROW = 230;
     public static final int LAYOUT_COLUMN = 204;
@@ -391,6 +393,7 @@
         map.put(LAYOUT_ROOT, RootLayoutComponent::read);
         map.put(LAYOUT_CONTENT, LayoutComponentContent::read);
         map.put(LAYOUT_BOX, BoxLayout::read);
+        map.put(LAYOUT_FIT_BOX, FitBoxLayout::read);
         map.put(LAYOUT_COLUMN, ColumnLayout::read);
         map.put(LAYOUT_COLLAPSIBLE_COLUMN, CollapsibleColumnLayout::read);
         map.put(LAYOUT_ROW, RowLayout::read);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
index f355676..c27ee32 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
@@ -17,11 +17,13 @@
 
 import android.annotation.NonNull;
 
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+
 /**
  * PaintOperation interface, used for operations aimed at painting (while any operation _can_ paint,
  * this make it a little more explicit)
  */
-public abstract class PaintOperation extends Operation {
+public abstract class PaintOperation extends Operation implements Serializable {
 
     @Override
     public void apply(@NonNull RemoteContext context) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
index 6073de6..3fff86c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Platform.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
@@ -47,6 +47,14 @@
     int getImageHeight(@NonNull Object image);
 
     /**
+     * Returns true if the platform-specific image object has format ALPHA_8
+     *
+     * @param image platform-specific image object
+     * @return whether or not the platform-specific image object has format ALPHA_8
+     */
+    boolean isAlpha8Image(@NonNull Object image);
+
+    /**
      * Converts a platform-specific path object into a platform-independent float buffer
      *
      * @param path
@@ -110,6 +118,11 @@
                 }
 
                 @Override
+                public boolean isAlpha8Image(@NonNull Object image) {
+                    throw new UnsupportedOperationException();
+                }
+
+                @Override
                 public float[] pathToFloatArray(@NonNull Object path) {
                     throw new UnsupportedOperationException();
                 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index e75bd30..c249adf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -98,6 +98,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.FitBoxLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout;
@@ -281,13 +282,7 @@
             int dstRight,
             int dstBottom,
             @Nullable String contentDescription) {
-        int imageId = mRemoteComposeState.dataGetId(image);
-        if (imageId == -1) {
-            imageId = mRemoteComposeState.cacheData(image);
-            byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
-            BitmapData.apply(
-                    mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe
-        }
+        int imageId = storeBitmap(image);
         int contentDescriptionId = 0;
         if (contentDescription != null) {
             contentDescriptionId = addText(contentDescription);
@@ -443,16 +438,7 @@
             float right,
             float bottom,
             @Nullable String contentDescription) {
-        int imageId = mRemoteComposeState.dataGetId(image);
-        if (imageId == -1) {
-            imageId = mRemoteComposeState.cacheData(image);
-            byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
-            int imageWidth = mPlatform.getImageWidth(image);
-            int imageHeight = mPlatform.getImageHeight(image);
-
-            BitmapData.apply(
-                    mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe
-        }
+        int imageId = storeBitmap(image);
         addDrawBitmap(imageId, left, top, right, bottom, contentDescription);
     }
 
@@ -523,15 +509,7 @@
             int scaleType,
             float scaleFactor,
             @Nullable String contentDescription) {
-        int imageId = mRemoteComposeState.dataGetId(image);
-        if (imageId == -1) {
-            imageId = mRemoteComposeState.cacheData(image);
-            byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
-            int imageWidth = mPlatform.getImageWidth(image);
-            int imageHeight = mPlatform.getImageHeight(image);
-
-            BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential pe
-        }
+        int imageId = storeBitmap(image);
         int contentDescriptionId = 0;
         if (contentDescription != null) {
             contentDescriptionId = addText(contentDescription);
@@ -559,16 +537,7 @@
      * @return id of the image useful with
      */
     public int addBitmap(@NonNull Object image) {
-        int imageId = mRemoteComposeState.dataGetId(image);
-        if (imageId == -1) {
-            imageId = mRemoteComposeState.cacheData(image);
-            byte[] data = mPlatform.imageToByteArray(image); // tODO: potential npe
-            int imageWidth = mPlatform.getImageWidth(image);
-            int imageHeight = mPlatform.getImageHeight(image);
-
-            BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data);
-        }
-        return imageId;
+        return storeBitmap(image);
     }
 
     /**
@@ -578,18 +547,7 @@
      * @return id of the image useful with
      */
     public int addBitmap(@NonNull Object image, @NonNull String name) {
-        int imageId = mRemoteComposeState.dataGetId(image);
-        if (imageId == -1) {
-            imageId = mRemoteComposeState.cacheData(image);
-            byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
-            int imageWidth = mPlatform.getImageWidth(image);
-            int imageHeight = mPlatform.getImageHeight(image);
-
-            BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data);
-            setBitmapName(imageId, name);
-        }
-
-        return imageId;
+        return storeBitmap(image);
     }
 
     /**
@@ -1393,7 +1351,7 @@
      * @return the id of the command representing long
      */
     public int addLong(long value) {
-        int id = mRemoteComposeState.cacheData(value);
+        int id = mRemoteComposeState.nextId();
         LongConstant.apply(mBuffer, id, value);
         return id;
     }
@@ -1405,7 +1363,7 @@
      * @return the id
      */
     public int addBoolean(boolean value) {
-        int id = mRemoteComposeState.cacheData(value);
+        int id = mRemoteComposeState.nextId();
         BooleanConstant.apply(mBuffer, id, value);
         return id;
     }
@@ -1821,33 +1779,14 @@
     }
 
     /**
-     * This defines the name of the color given the id.
-     *
-     * @param id of the color
-     * @param name Name of the color
-     */
-    public void setColorName(int id, @NonNull String name) {
-        NamedVariable.apply(mBuffer, id, NamedVariable.COLOR_TYPE, name);
-    }
-
-    /**
-     * This defines the name of the string given the id
-     *
-     * @param id of the string
-     * @param name name of the string
-     */
-    public void setStringName(int id, @NonNull String name) {
-        NamedVariable.apply(mBuffer, id, NamedVariable.STRING_TYPE, name);
-    }
-
-    /**
-     * This defines the name of the float given the id
+     * This defines the name of a type of given object
      *
      * @param id of the float
      * @param name name of the float
+     * @param type the type of variable NamedVariable.COLOR_TYPE, STRING_TYPE, etc
      */
-    public void setFloatName(int id, String name) {
-        NamedVariable.apply(mBuffer, id, NamedVariable.FLOAT_TYPE, name);
+    public void setNamedVariable(int id, @NonNull String name, int type) {
+        NamedVariable.apply(mBuffer, id, type, name);
     }
 
     /**
@@ -2139,6 +2078,19 @@
     }
 
     /**
+     * Add a fitbox start tag
+     *
+     * @param componentId component id
+     * @param animationId animation id
+     * @param horizontal horizontal alignment
+     * @param vertical vertical alignment
+     */
+    public void addFitBoxStart(int componentId, int animationId, int horizontal, int vertical) {
+        mLastComponentId = getComponentId(componentId);
+        FitBoxLayout.apply(mBuffer, mLastComponentId, animationId, horizontal, vertical);
+    }
+
+    /**
      * Add a row start tag
      *
      * @param componentId component id
@@ -2439,4 +2391,35 @@
     public void drawComponentContent() {
         DrawContent.apply(mBuffer);
     }
+
+    /**
+     * Ensures the bitmap is stored.
+     *
+     * @param image the bitbap to store
+     * @return the id of the bitmap
+     */
+    private int storeBitmap(Object image) {
+        int imageId = mRemoteComposeState.dataGetId(image);
+        if (imageId == -1) {
+            imageId = mRemoteComposeState.cacheData(image);
+            byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
+            short imageWidth = (short) mPlatform.getImageWidth(image);
+            short imageHeight = (short) mPlatform.getImageHeight(image);
+            if (mPlatform.isAlpha8Image(image)) {
+                BitmapData.apply(
+                        mBuffer,
+                        imageId,
+                        BitmapData.TYPE_PNG_ALPHA_8,
+                        imageWidth,
+                        BitmapData.ENCODING_INLINE,
+                        imageHeight,
+                        data); // todo: potential npe
+            } else {
+                BitmapData.apply(
+                        mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe
+            }
+        }
+
+        return imageId;
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
index 363b82b..83c0619 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -27,6 +27,7 @@
 import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 
 /**
@@ -36,7 +37,7 @@
 public class RemoteComposeState implements CollectionsAccess {
     public static final int START_ID = 42;
     //    private static final int MAX_FLOATS = 500;
-    private static final int MAX_COLORS = 200;
+    private static int sMaxColors = 200;
 
     private static final int MAX_DATA = 1000;
     private final IntMap<Object> mIntDataMap = new IntMap<>();
@@ -52,7 +53,7 @@
     private final IntMap<Object> mPathMap = new IntMap<>();
     private final IntMap<float[]> mPathData = new IntMap<>();
 
-    private final boolean[] mColorOverride = new boolean[MAX_COLORS];
+    private boolean[] mColorOverride = new boolean[sMaxColors];
     @NonNull private final IntMap<ArrayAccess> mCollectionMap = new IntMap<>();
 
     private final boolean[] mDataOverride = new boolean[MAX_DATA];
@@ -318,7 +319,7 @@
      * @param color
      */
     public void updateColor(int id, int color) {
-        if (mColorOverride[id]) {
+        if (id < sMaxColors && mColorOverride[id]) {
             return;
         }
         mColorMap.put(id, color);
@@ -342,6 +343,10 @@
      * @param color
      */
     public void overrideColor(int id, int color) {
+        if (id >= sMaxColors) {
+            sMaxColors *= 2;
+            mColorOverride = Arrays.copyOf(mColorOverride, sMaxColors);
+        }
         mColorOverride[id] = true;
         mColorMap.put(id, color);
         updateListeners(id);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index 622f0c8..c6b17e4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -211,6 +211,14 @@
     public abstract void clearNamedFloatOverride(String floatName);
 
     /**
+     * Set the value of a named long. This modifies the content of a LongConstant
+     *
+     * @param name the name of the float to override
+     * @param value Override the default float
+     */
+    public abstract void setNamedLong(String name, long value);
+
+    /**
      * Set the value of a named Object. This overrides the Object in the document
      *
      * @param dataName the name of the Object to override
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
index 13e6f38..255d7a4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
@@ -72,6 +72,9 @@
     /** The data is encoded as RAW 8888 bit */
     public static final short TYPE_RAW8888 = 3;
 
+    /** The data is encoded as PNG_8888 but decoded as ALPHA_8 */
+    public static final short TYPE_PNG_ALPHA_8 = 4;
+
     /**
      * create a bitmap structure
      *
@@ -136,6 +139,15 @@
     }
 
     /**
+     * The type of the image
+     *
+     * @return the type of the image
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
      * Add the image to the document
      *
      * @param buffer document to write to
@@ -195,6 +207,21 @@
         int imageId = buffer.readInt();
         int width = buffer.readInt();
         int height = buffer.readInt();
+        int type;
+        if (width > 0xffff) {
+            type = width >> 16;
+            width = width & 0xffff;
+        } else {
+            type = TYPE_PNG_8888;
+        }
+
+        int encoding;
+        if (height > 0xffff) {
+            encoding = height >> 16;
+            height = height & 0xffff;
+        } else {
+            encoding = ENCODING_INLINE;
+        }
         if (width < 1
                 || height < 1
                 || height > MAX_IMAGE_DIMENSION
@@ -202,7 +229,10 @@
             throw new RuntimeException("Dimension of image is invalid " + width + "x" + height);
         }
         byte[] bitmap = buffer.readBuffer();
-        operations.add(new BitmapData(imageId, width, height, bitmap));
+        BitmapData bitmapData = new BitmapData(imageId, width, height, bitmap);
+        bitmapData.mType = (short) type;
+        bitmapData.mEncoding = (short) encoding;
+        operations.add(bitmapData);
     }
 
     /**
@@ -244,7 +274,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("imageId", mImageId)
                 .add("imageWidth", mImageWidth)
                 .add("imageHeight", mImageHeight)
@@ -275,6 +305,8 @@
                 return "TYPE_RAW8";
             case TYPE_RAW8888:
                 return "TYPE_RAW8888";
+            case TYPE_PNG_ALPHA_8:
+                return "TYPE_PNG_ALPHA_8";
             default:
                 return "TYPE_INVALID";
         }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java
index 078ce98..70bda6d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java
@@ -221,6 +221,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("id", mId);
+        serializer.addType(CLASS_NAME).add("id", mId);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
index 00ac9c2..7a8373b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
@@ -237,7 +237,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("id", mId)
                 .add("contentDescriptionId", mContentDescription)
                 .add("left", mLeft, mOutLeft)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
index e7dc405..03bdb70 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
@@ -133,7 +133,7 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("id", mId).add("regionOp", regionOpToString());
+        serializer.addType(CLASS_NAME).add("id", mId).add("regionOp", regionOpToString());
     }
 
     String regionOpToString() {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
index 1646146..8b1e96a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
@@ -118,6 +118,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serialize(serializer, "left", "top", "right", "bottom").add("type", CLASS_NAME);
+        serialize(serializer, "left", "top", "right", "bottom").addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
index 333ffaa..7b12f4a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
@@ -133,7 +133,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("color", Utils.colorInt(mColor))
                 .add("colorId", mColorId);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
index d5af791..25323a8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
@@ -507,7 +507,7 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("id", mId);
+        serializer.addType(CLASS_NAME).add("id", mId);
         switch (mMode) {
             case COLOR_COLOR_INTERPOLATE:
             case ID_COLOR_INTERPOLATE:
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
index cd13d25..5335e4f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
@@ -171,7 +171,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("valueId", mValueId)
                 .add("componentValueType", typeToString(mType))
                 .add("componentId", mComponentID);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
index fb3abdb..20bebaa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
@@ -148,8 +148,9 @@
         return mValues.length;
     }
 
+    @SuppressWarnings("JdkImmutableCollections")
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("id", mId).add("values", List.of(mValues));
+        serializer.addType(CLASS_NAME).add("id", mId).add("values", List.of(mValues));
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
index 58fd74f..af660f3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
@@ -150,8 +150,9 @@
         return 0;
     }
 
+    @SuppressWarnings("JdkImmutableCollections")
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("id", mId).add("ids", List.of(mIds));
+        serializer.addType(CLASS_NAME).add("id", mId).add("ids", List.of(mIds));
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
index a427836..5024164 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
@@ -141,6 +141,6 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serialize(serializer, "left", "top", "right", "bottom", "startAngle", "sweepAngle")
-                .add("type", CLASS_NAME);
+                .addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
index 40d3bed..e3b53a1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
@@ -28,6 +28,7 @@
 import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 
 import java.util.List;
 
@@ -185,4 +186,16 @@
     public void paint(@NonNull PaintContext context) {
         context.drawBitmap(mId, mOutputLeft, mOutputTop, mOutputRight, mOutputBottom);
     }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        serializer
+                .addType(CLASS_NAME)
+                .add("imageId", mId)
+                .add("contentDescriptionId", mDescriptionId)
+                .add("left", mLeft, mOutputLeft)
+                .add("top", mTop, mOutputTop)
+                .add("right", mRight, mOutputRight)
+                .add("bottom", mBottom, mOutputBottom);
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java
index 258988e..bff87fd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java
@@ -28,6 +28,7 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 
 import java.util.List;
 
@@ -229,4 +230,16 @@
             xPos = xPos2 + glyph.mMarginRight;
         }
     }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        serializer
+                .addType(CLASS_NAME)
+                .add("textId", mTextID)
+                .add("bitmapFontId", mBitmapFontID)
+                .add("start", mStart)
+                .add("end", mEnd)
+                .add("x", mX, mOutX)
+                .add("y", mY, mOutY);
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
index 013dd1a..39d85af 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
@@ -25,6 +25,7 @@
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
 import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 
 import java.util.List;
 
@@ -230,4 +231,20 @@
                 mDstBottom,
                 mContentDescId);
     }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        serializer
+                .addType(CLASS_NAME)
+                .add("imageId", mImageId)
+                .add("contentDescriptionId", mContentDescId)
+                .add("srcLeft", mSrcLeft)
+                .add("srcTop", mSrcTop)
+                .add("srcRight", mSrcRight)
+                .add("srcBottom", mSrcBottom)
+                .add("dstLeft", mDstLeft)
+                .add("dstTop", mDstTop)
+                .add("dstRight", mDstRight)
+                .add("dstBottom", mDstBottom);
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
index e1070f9..827e569 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
@@ -28,6 +28,7 @@
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.ImageScaling;
 import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 
 import java.util.List;
 
@@ -374,4 +375,46 @@
                 mContentDescId);
         context.restore();
     }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        serializer
+                .addType(CLASS_NAME)
+                .add("imageId", mImageId)
+                .add("contentDescriptionId", mContentDescId)
+                .add("scaleType", getScaleTypeString())
+                .add("mode", mMode)
+                .add("scaleFactor", mScaleFactor, mOutScaleFactor)
+                .add("srcLeft", mSrcLeft, mOutSrcLeft)
+                .add("srcTop", mSrcTop, mOutSrcTop)
+                .add("srcRight", mSrcRight, mOutSrcRight)
+                .add("srcBottom", mSrcBottom, mOutSrcBottom)
+                .add("dstLeft", mDstLeft, mOutDstLeft)
+                .add("dstTop", mDstTop, mOutDstTop)
+                .add("dstRight", mDstRight, mOutDstRight)
+                .add("dstBottom", mDstBottom, mOutDstBottom);
+    }
+
+    private String getScaleTypeString() {
+        switch (mScaleType) {
+            case SCALE_NONE:
+                return "SCALE_NONE";
+            case SCALE_INSIDE:
+                return "SCALE_INSIDE";
+            case SCALE_FILL_WIDTH:
+                return "SCALE_FILL_WIDTH";
+            case SCALE_FILL_HEIGHT:
+                return "SCALE_FILL_HEIGHT";
+            case SCALE_FIT:
+                return "SCALE_FIT";
+            case SCALE_CROP:
+                return "SCALE_CROP";
+            case SCALE_FILL_BOUNDS:
+                return "SCALE_FILL_BOUNDS";
+            case SCALE_FIXED_SCALE:
+                return "SCALE_FIXED_SCALE";
+            default:
+                return "INVALID_SCALE_TYPE";
+        }
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
index dfc89b1..538cbaf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
@@ -112,6 +112,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serialize(serializer, "cx", "cy", "radius").add("type", CLASS_NAME);
+        serialize(serializer, "cx", "cy", "radius").addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java
index e2e22ac..4d2a939 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java
@@ -114,6 +114,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME);
+        serializer.addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
index 461dece..97e5057 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
@@ -140,6 +140,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serialize(serializer, "startX", "startY", "endX", "endY").add("type", CLASS_NAME);
+        serialize(serializer, "startX", "startY", "endX", "endY").addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
index d0a5adc..5d619ba 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
@@ -110,6 +110,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serialize(serializer, "left", "top", "right", "bottom").add("type", CLASS_NAME);
+        serialize(serializer, "left", "top", "right", "bottom").addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
index 3fd8bb4..6e29927 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
@@ -112,6 +112,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("id", mId).add("start", mStart).add("end", mEnd);
+        serializer.addType(CLASS_NAME).add("id", mId).add("start", mStart).add("end", mEnd);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
index f6aa30f..15f3ced 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
@@ -106,6 +106,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serialize(serializer, "left", "top", "right", "bottom").add("type", CLASS_NAME);
+        serialize(serializer, "left", "top", "right", "bottom").addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
index 67338c1..31d9b6a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
@@ -127,6 +127,6 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serialize(serializer, "left", "top", "right", "bottom", "rx", "sweepAngle")
-                .add("type", CLASS_NAME);
+                .addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
index 78f64a5..19f1623 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
@@ -128,6 +128,6 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serialize(serializer, "left", "top", "right", "bottom", "startAngle", "sweepAngle")
-                .add("type", CLASS_NAME);
+                .addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
index 8adba1d..ee1689c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
@@ -28,6 +28,7 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 
 import java.util.List;
 
@@ -205,4 +206,17 @@
     public void paint(@NonNull PaintContext context) {
         context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mOutX, mOutY, mRtl);
     }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        serializer
+                .addType(CLASS_NAME)
+                .add("start", mStart)
+                .add("end", mEnd)
+                .add("contextStart", mContextStart)
+                .add("contextEnd", mContextEnd)
+                .add("x", mX, mOutX)
+                .add("y", mY, mOutY)
+                .add("rtl", mRtl);
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
index 92469d1..1d917d5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
@@ -244,7 +244,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("textId", mTextID)
                 .add("x", mX, mOutX)
                 .add("y", mY, mOutY)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
index 1f7910e..f382440 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
@@ -159,7 +159,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("pathId", mPathId)
                 .add("textId", mTextId)
                 .add("vOffset", mVOffset, mOutVOffset)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
index e288394..4ca995b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
@@ -28,6 +28,7 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 
 import java.util.List;
 
@@ -171,4 +172,15 @@
     public void paint(@NonNull PaintContext context) {
         context.drawTweenPath(mPath1Id, mPath2Id, mOutTween, mOutStart, mOutStop);
     }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        serializer
+                .addType(CLASS_NAME)
+                .add("path1Id", mPath1Id)
+                .add("path2Id", mPath2Id)
+                .add("tween", mTween, mOutTween)
+                .add("start", mStart, mOutStart)
+                .add("stop", mStop, mOutStop);
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
index 7f3c3ed..233e246 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
@@ -123,6 +123,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+        serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
index c1fa898..eba201b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
@@ -160,6 +160,9 @@
                 mLastAnimatedValue = lastComputedValue;
                 context.loadFloat(mId, lastComputedValue);
                 context.needsRepaint();
+                if (mFloatAnimation.isPropagate()) {
+                    markDirty();
+                }
             }
         } else if (mSpring != null) {
             float lastComputedValue = mSpring.get(t - mLastChange);
@@ -169,8 +172,12 @@
                 context.needsRepaint();
             }
         } else {
-            float v =
-                    mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+            float v = 0;
+            try {
+                v = mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+            } catch (Exception e) {
+                throw new RuntimeException(this.toString() + " len = " + mPreCalcValue.length, e);
+            }
             if (mFloatAnimation != null) {
                 mFloatAnimation.setTargetValue(v);
             }
@@ -344,7 +351,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.EXPRESSION)
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("id", mId)
                 .addFloatExpressionSrc("srcValues", mSrcValue)
                 .add("animation", mFloatAnimation);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java
index eccc00a..8559294 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java
@@ -32,8 +32,10 @@
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /** This provides the command to call a floatfunction defined in floatfunction */
@@ -182,4 +184,13 @@
         }
         mFunction.execute(remoteContext);
     }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        serializer
+                .addType(CLASS_NAME)
+                .add("id", mId)
+                .add("args", Collections.singletonList(mArgs))
+                .add("outArgs", Collections.singletonList(mOutArgs));
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java
index fb4a5e4..9ef1b3b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java
@@ -26,7 +26,9 @@
 import com.android.internal.widget.remotecompose.core.PaintOperation;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 
+import java.util.Collections;
 import java.util.List;
 
 /** Operation to extract meta Attributes from image data objects */
@@ -162,4 +164,25 @@
                 break;
         }
     }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        serializer
+                .addType(CLASS_NAME)
+                .add("id", mId)
+                .add("imageId", mImageId)
+                .add("args", Collections.singletonList(mArgs))
+                .addType(typeToString());
+    }
+
+    private String typeToString() {
+        switch (mType) {
+            case IMAGE_WIDTH:
+                return "IMAGE_WIDTH";
+            case IMAGE_HEIGHT:
+                return "IMAGE_HEIGHT";
+            default:
+                return "INVALID_TYPE";
+        }
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
index 2a5260c..53b3c89 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
@@ -233,7 +233,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.EXPRESSION)
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("id", mId)
                 .add("mask", mId)
                 .addIntExpressionSrc("srcValues", mSrcValue, mMask);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
index 64df19d..d2b38f42 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
@@ -102,6 +102,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME);
+        serializer.addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
index 9c4df0b..5990b4b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
@@ -114,6 +114,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serialize(serializer, "rotate", "pivotX", "pivotY").add("type", CLASS_NAME);
+        serialize(serializer, "rotate", "pivotX", "pivotY").addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
index 0e6de0d..06da156 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
@@ -100,6 +100,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME);
+        serializer.addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
index b6e5cbc..20d4065 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
@@ -106,6 +106,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serialize(serializer, "scaleX", "scaleY", "pivotX", "pivotY").add("type", CLASS_NAME);
+        serialize(serializer, "scaleX", "scaleY", "pivotX", "pivotY").addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
index f9a589c..6d1c503 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
@@ -103,6 +103,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serialize(serializer, "skewX", "skewY").add("type", CLASS_NAME);
+        serialize(serializer, "skewX", "skewY").addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
index de783bf..e21f133 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
@@ -102,6 +102,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serialize(serializer, "dx", "dy").add("type", CLASS_NAME);
+        serialize(serializer, "dx", "dy").addType(CLASS_NAME);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
index 96628fd..9a88085 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
@@ -25,7 +25,6 @@
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
-import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
 import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 import com.android.internal.widget.remotecompose.core.serialize.Serializable;
 
@@ -43,6 +42,8 @@
     public static final int FLOAT_TYPE = 1;
     public static final int STRING_TYPE = 0;
     public static final int IMAGE_TYPE = 3;
+    public static final int INT_TYPE = 4;
+    public static final int LONG_TYPE = 5;
 
     public NamedVariable(int varId, int varType, @NonNull String name) {
         this.mVarId = varId;
@@ -122,7 +123,7 @@
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Add a string name for an ID")
-                .field(DocumentedOperation.INT, "varId", "id to label")
+                .field(INT, "varId", "id to label")
                 .field(INT, "varType", "The type of variable")
                 .field(UTF8, "name", "String");
     }
@@ -141,7 +142,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("varId", mVarId)
                 .add("varName", mVarName)
                 .add("varType", typeToString());
@@ -157,6 +158,8 @@
                 return "STRING_TYPE";
             case IMAGE_TYPE:
                 return "IMAGE_TYPE";
+            case INT_TYPE:
+                return "INT_TYPE";
             default:
                 return "INVALID_TYPE";
         }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
index 8389aa7..70197c6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
@@ -131,6 +131,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("paintBundle", mPaintData);
+        serializer.addType(CLASS_NAME).add("paintBundle", mPaintData);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java
index 8d19c94..f9fdfdf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java
@@ -33,6 +33,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.Container;
 import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -292,4 +293,9 @@
             }
         }
     }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        serializer.addType(CLASS_NAME).add("id", mId);
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
index 8f353ce..8a747e1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
@@ -258,9 +258,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer
-                .add("type", CLASS_NAME)
-                .add("id", mInstanceId)
-                .add("path", pathString(mFloatPath));
+        serializer.addType(CLASS_NAME).add("id", mInstanceId).add("path", pathString(mFloatPath));
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
index 7aa3390..78e3b9e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
@@ -242,9 +242,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer
-                .add("type", CLASS_NAME)
-                .add("id", mInstanceId)
-                .add("path", pathString(mFloatPath));
+        serializer.addType(CLASS_NAME).add("id", mInstanceId).add("path", pathString(mFloatPath));
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
index 9564f15..cedc4f3b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
@@ -243,9 +243,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer
-                .add("type", CLASS_NAME)
-                .add("id", mInstanceId)
-                .add("path", pathString(mFloatPath));
+        serializer.addType(CLASS_NAME).add("id", mInstanceId).add("path", pathString(mFloatPath));
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
index c5add57..09b29e8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
@@ -161,7 +161,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("outId", mOutId)
                 .add("pathId1", mPathId1)
                 .add("pathId2", mPathId2)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
index a6a8a81..214d240 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
@@ -133,6 +133,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("contentDescriptionId", mContentDescription);
+        serializer.addType(CLASS_NAME).add("contentDescriptionId", mContentDescription);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
index 5f6162b..013b6f6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
@@ -390,7 +390,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("shaderTextId", mShaderTextId)
                 .add("shaderID", mShaderID)
                 .add("uniformRawFloatMap", mUniformRawFloatMap)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java
index 3e72995d..36f25c6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java
@@ -173,7 +173,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("id", mId)
                 .add("textId", mTextId)
                 .add("measureType", typeToString());
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
index 419e6d0..67773d1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
@@ -136,6 +136,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("textId", mTextId).add("text", mText);
+        serializer.addType(CLASS_NAME).add("textId", mTextId).add("text", mText);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
index 6b2f49b..f22369f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
@@ -215,7 +215,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("textId", mTextId)
                 .add("value", mValue, mOutValue)
                 .add("digitsBefore", mDigitsBefore)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
index e8865c2..fa44bf1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
@@ -156,7 +156,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("textId", mTextId)
                 .add("dataSetId", mDataSetId)
                 .add("indexId", mIndex, mOutIndex);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
index de20255..5ec3290 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
@@ -149,7 +149,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("textId", mTextId)
                 .add("dataSetId", mDataSetId)
                 .add("indexId", mIndex, mOutIndex);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
index 58cd68e..3559d1db 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
@@ -27,6 +27,7 @@
 import com.android.internal.widget.remotecompose.core.PaintOperation;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 
 import java.util.List;
 
@@ -161,4 +162,33 @@
                 break;
         }
     }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        serializer
+                .addType(CLASS_NAME)
+                .add("id", mId)
+                .add("textId", mTextId)
+                .add("measureType", typeToString());
+    }
+
+    private String typeToString() {
+        int val = mType & 255;
+        switch (val) {
+            case MEASURE_WIDTH:
+                return "MEASURE_WIDTH";
+            case MEASURE_HEIGHT:
+                return "MEASURE_HEIGHT";
+            case MEASURE_LEFT:
+                return "MEASURE_LEFT";
+            case MEASURE_TOP:
+                return "MEASURE_TOP";
+            case MEASURE_RIGHT:
+                return "MEASURE_RIGHT";
+            case MEASURE_BOTTOM:
+                return "MEASURE_BOTTOM";
+            default:
+                return "INVALID_TYPE";
+        }
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
index 262916d..1239b56 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
@@ -132,7 +132,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("id", mTextId)
                 .add("leftId", mSrcId1)
                 .add("rightId", mSrcId2);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
index afb84b5..fd9a2bf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
@@ -27,12 +27,14 @@
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 import com.android.internal.widget.remotecompose.core.types.LongConstant;
 
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 /** Operation to perform time related calculation */
@@ -292,4 +294,48 @@
                 break;
         }
     }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        serializer
+                .addType(CLASS_NAME)
+                .add("id", mId)
+                .add("timeId", mTimeId)
+                .addType(getTypeString())
+                .add("args", Collections.singletonList(mArgs));
+    }
+
+    private String getTypeString() {
+        int val = mType & 255;
+        switch (val) {
+            case TIME_FROM_NOW_SEC:
+                return "TIME_FROM_NOW_SEC";
+            case TIME_FROM_NOW_MIN:
+                return "TIME_FROM_NOW_MIN";
+            case TIME_FROM_NOW_HR:
+                return "TIME_FROM_NOW_HR";
+            case TIME_FROM_ARG_SEC:
+                return "TIME_FROM_ARG_SEC";
+            case TIME_FROM_ARG_MIN:
+                return "TIME_FROM_ARG_MIN";
+            case TIME_FROM_ARG_HR:
+                return "TIME_FROM_ARG_HR";
+            case TIME_IN_SEC:
+                return "TIME_IN_SEC";
+            case TIME_IN_MIN:
+                return "TIME_IN_MIN";
+            case TIME_IN_HR:
+                return "TIME_IN_HR";
+            case TIME_DAY_OF_MONTH:
+                return "TIME_DAY_OF_MONTH";
+            case TIME_MONTH_VALUE:
+                return "TIME_MONTH_VALUE";
+            case TIME_DAY_OF_WEEK:
+                return "TIME_DAY_OF_WEEK";
+            case TIME_YEAR:
+                return "TIME_YEAR";
+            default:
+                return "INVALID_TIME_TYPE";
+        }
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
index 2591a4c..f246729 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
@@ -716,9 +716,9 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("id", mId)
-                .add("mDefValue", mDefValue, mOutDefValue)
+                .add("defValue", mDefValue, mOutDefValue)
                 .add("min", mMin, mOutMin)
                 .add("max", mMax, mOutMax)
                 .add("mode", mMode)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
index 9dc2a49..b98a017 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
@@ -129,6 +129,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", "AnimatableValue").add("id", mId);
+        serializer.addType("AnimatableValue").add("id", mId);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java
index 3e7f1d3..25a10ab 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java
@@ -156,7 +156,7 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("list", mList);
+        serializer.addType(CLASS_NAME).add("list", mList);
     }
 
     /**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
index 8b13c13..7ee1490 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
@@ -253,6 +253,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.addTags(SerializeTags.MODIFIER).add("type", "ClickModifierOperation");
+        serializer.addTags(SerializeTags.MODIFIER).addType("ClickModifierOperation");
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
index c736436..b30dade 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
@@ -57,8 +57,8 @@
     protected float mHeight;
     @Nullable protected Component mParent;
     protected int mAnimationId = -1;
-    @NonNull public Visibility mVisibility = Visibility.VISIBLE;
-    @NonNull public Visibility mScheduledVisibility = Visibility.VISIBLE;
+    public int mVisibility = Visibility.VISIBLE;
+    public int mScheduledVisibility = Visibility.VISIBLE;
     @NonNull public ArrayList<Operation> mList = new ArrayList<>();
     public PaintOperation mPreTranslate; // todo, can we initialize this here and make it NonNull?
     public boolean mNeedsMeasure = true;
@@ -288,22 +288,42 @@
     }
 
     /**
-     * Returns the intrinsic width of the layout
+     * Returns the min intrinsic width of the layout
      *
      * @param context
      * @return the width in pixels
      */
-    public float intrinsicWidth(@Nullable RemoteContext context) {
+    public float minIntrinsicWidth(@Nullable RemoteContext context) {
         return getWidth();
     }
 
     /**
-     * Returns the intrinsic height of the layout
+     * Returns the max intrinsic width of the layout
+     *
+     * @param context
+     * @return the width in pixels
+     */
+    public float maxIntrinsicWidth(@Nullable RemoteContext context) {
+        return getWidth();
+    }
+
+    /**
+     * Returns the min intrinsic height of the layout
      *
      * @param context
      * @return the height in pixels
      */
-    public float intrinsicHeight(@Nullable RemoteContext context) {
+    public float minIntrinsicHeight(@Nullable RemoteContext context) {
+        return getHeight();
+    }
+
+    /**
+     * Returns the max intrinsic height of the layout
+     *
+     * @param context
+     * @return the height in pixels
+     */
+    public float maxIntrinsicHeight(@Nullable RemoteContext context) {
         return getHeight();
     }
 
@@ -338,10 +358,119 @@
         // Nothing here
     }
 
-    public enum Visibility {
-        GONE,
-        VISIBLE,
-        INVISIBLE
+    public static class Visibility {
+
+        public static final int GONE = 0;
+        public static final int VISIBLE = 1;
+        public static final int INVISIBLE = 2;
+        public static final int OVERRIDE_GONE = 16;
+        public static final int OVERRIDE_VISIBLE = 32;
+        public static final int OVERRIDE_INVISIBLE = 64;
+        public static final int CLEAR_OVERRIDE = 128;
+
+        /**
+         * Returns a string representation of the field
+         *
+         * @param value
+         * @return
+         */
+        public static String toString(int value) {
+            switch (value) {
+                case GONE:
+                    return "GONE";
+                case VISIBLE:
+                    return "VISIBLE";
+                case INVISIBLE:
+                    return "INVISIBLE";
+            }
+            if ((value >> 4) > 0) {
+                if ((value & OVERRIDE_GONE) == OVERRIDE_GONE) {
+                    return "OVERRIDE_GONE";
+                }
+                if ((value & OVERRIDE_VISIBLE) == OVERRIDE_VISIBLE) {
+                    return "OVERRIDE_VISIBLE";
+                }
+                if ((value & OVERRIDE_INVISIBLE) == OVERRIDE_INVISIBLE) {
+                    return "OVERRIDE_INVISIBLE";
+                }
+            }
+            return "" + value;
+        }
+
+        /**
+         * Returns true if gone
+         *
+         * @param value
+         * @return
+         */
+        public static boolean isGone(int value) {
+            if ((value >> 4) > 0) {
+                return (value & OVERRIDE_GONE) == OVERRIDE_GONE;
+            }
+            return value == GONE;
+        }
+
+        /**
+         * Returns true if visible
+         *
+         * @param value
+         * @return
+         */
+        public static boolean isVisible(int value) {
+            if ((value >> 4) > 0) {
+                return (value & OVERRIDE_VISIBLE) == OVERRIDE_VISIBLE;
+            }
+            return value == VISIBLE;
+        }
+
+        /**
+         * Returns true if invisible
+         *
+         * @param value
+         * @return
+         */
+        public static boolean isInvisible(int value) {
+            if ((value >> 4) > 0) {
+                return (value & OVERRIDE_INVISIBLE) == OVERRIDE_INVISIBLE;
+            }
+            return value == INVISIBLE;
+        }
+
+        /**
+         * Returns true if the field has an override
+         *
+         * @param value
+         * @return
+         */
+        public static boolean hasOverride(int value) {
+            return (value >> 4) > 0;
+        }
+
+        /**
+         * Clear the override values
+         *
+         * @param value
+         * @return
+         */
+        public static int clearOverride(int value) {
+            return value & 15;
+        }
+
+        /**
+         * Add an override value
+         *
+         * @param value
+         * @param visibility
+         * @return
+         */
+        public static int add(int value, int visibility) {
+            int v = value & 15;
+            v += visibility;
+            if ((v & CLEAR_OVERRIDE) == CLEAR_OVERRIDE) {
+                v = v & 15;
+            }
+            return v;
+        }
     }
 
     /**
@@ -350,13 +479,28 @@
      * @return
      */
     public boolean isVisible() {
-        if (mVisibility != Visibility.VISIBLE || mParent == null) {
-            return mVisibility == Visibility.VISIBLE;
+        if (mParent == null || !Visibility.isVisible(mVisibility)) {
+            return Visibility.isVisible(mVisibility);
         }
-        if (mParent != null) { // TODO: this is always true -- bbade@
-            return mParent.isVisible();
-        }
-        return true;
+        return mParent.isVisible();
+    }
+
+    /**
+     * Returns true if the component is gone
+     *
+     * @return
+     */
+    public boolean isGone() {
+        return Visibility.isGone(mVisibility);
+    }
+
+    /**
+     * Returns true if the component is invisible
+     *
+     * @return
+     */
+    public boolean isInvisible() {
+        return Visibility.isInvisible(mVisibility);
     }
 
     /**
@@ -364,7 +508,7 @@
      *
      * @param visibility can be VISIBLE, INVISIBLE or GONE
      */
-    public void setVisibility(@NonNull Visibility visibility) {
+    public void setVisibility(int visibility) {
         if (visibility != mVisibility || visibility != mScheduledVisibility) {
             mScheduledVisibility = visibility;
             invalidateMeasure();
@@ -705,7 +849,7 @@
                 + "] "
                 + textContent()
                 + " Visibility ("
-                + mVisibility
+                + Visibility.toString(mVisibility)
                 + ") ";
     }
 
@@ -732,7 +876,7 @@
                         + ", "
                         + mHeight
                         + "] "
-                        + mVisibility;
+                        + Visibility.toString(mVisibility);
         //        + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]"
         serializer.append(indent, content);
     }
@@ -966,7 +1110,7 @@
         if (applyAnimationAsNeeded(context)) {
             return;
         }
-        if (mVisibility == Visibility.GONE || mVisibility == Visibility.INVISIBLE) {
+        if (isGone() || isInvisible()) {
             return;
         }
         paintingComponent(context);
@@ -1071,13 +1215,13 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer.addTags(SerializeTags.COMPONENT);
-        serializer.add("type", getSerializedName());
+        serializer.addType(getSerializedName());
         serializer.add("id", mComponentId);
         serializer.add("x", mX);
         serializer.add("y", mY);
         serializer.add("width", mWidth);
         serializer.add("height", mHeight);
-        serializer.add("visibility", mVisibility);
+        serializer.add("visibility", Visibility.toString(mVisibility));
         serializer.add("list", mList);
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java
index e277d49..0e629c5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java
@@ -27,6 +27,7 @@
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
 import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -228,4 +229,12 @@
     public void setProcess(ImpulseProcess impulseProcess) {
         mProcess = impulseProcess;
     }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        serializer
+                .addType(CLASS_NAME)
+                .add("duration", mDuration, mOutDuration)
+                .add("startAt", mStartAt, mOutStartAt);
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java
index 8c9dd76..83d4d38 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java
@@ -157,6 +157,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("list", mList);
+        serializer.addType(CLASS_NAME).add("list", mList);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
index 2b63cf2..dda328f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
@@ -206,12 +206,12 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("indexVariableId", mIndexVariableId)
                 .add("until", mUntil, mUntilOut)
                 .add("from", mFrom, mFromOut)
                 .add("step", mStep, mStepOut)
-                .add("mUntilOut", mUntilOut)
+                .add("untilOut", mUntilOut)
                 .add("list", mList);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
index f2503b2..77d3dae 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
@@ -77,7 +77,7 @@
                 + " x "
                 + mHeight
                 + ") "
-                + mVisibility;
+                + Visibility.toString(mVisibility);
     }
 
     @Override
@@ -97,7 +97,7 @@
                         + ", "
                         + mHeight
                         + "] "
-                        + mVisibility);
+                        + Visibility.toString(mVisibility));
     }
 
     /**
@@ -282,6 +282,6 @@
     public void serialize(MapSerializer serializer) {
         super.serialize(serializer);
         serializer.addTags(SerializeTags.COMPONENT);
-        serializer.add("type", "RootLayoutComponent");
+        serializer.addType("RootLayoutComponent");
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
index 62b1b6c..283bc7a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
@@ -129,6 +129,6 @@
     @Override
     public void serialize(MapSerializer serializer) {
         super.serialize(serializer);
-        serializer.add("type", "TouchCancelModifierOperation");
+        serializer.addType("TouchCancelModifierOperation");
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
index 5289fda..b010c14 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
@@ -131,6 +131,6 @@
     @Override
     public void serialize(MapSerializer serializer) {
         super.serialize(serializer);
-        serializer.add("type", "TouchDownModifierOperation");
+        serializer.addType("TouchDownModifierOperation");
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
index 576c5e9..bc5c10b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
@@ -129,6 +129,6 @@
     @Override
     public void serialize(MapSerializer serializer) {
         super.serialize(serializer);
-        serializer.add("type", "TouchUpModifierOperation");
+        serializer.addType("TouchUpModifierOperation");
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
index e5cd485..1a60451d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
@@ -143,7 +143,7 @@
      */
     public void paint(@NonNull PaintContext context) {
         if (mOriginal.getVisibility() != mTarget.getVisibility()) {
-            if (mTarget.getVisibility() == Component.Visibility.GONE) {
+            if (mTarget.isGone()) {
                 switch (mExitAnimation) {
                     case PARTICLE:
                         // particleAnimation(context, component, original, target, vp)
@@ -229,8 +229,7 @@
                         mParticleAnimation.animate(context, mComponent, mOriginal, mTarget, mVp);
                         break;
                 }
-            } else if (mOriginal.getVisibility() == Component.Visibility.GONE
-                    && mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+            } else if (mOriginal.isGone() && mTarget.isVisible()) {
                 switch (mEnterAnimation) {
                     case ROTATE:
                         float px = mTarget.getX() + mTarget.getW() / 2f;
@@ -323,7 +322,7 @@
             } else {
                 mComponent.paintingComponent(context);
             }
-        } else if (mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+        } else if (mTarget.isVisible()) {
             mComponent.paintingComponent(context);
         }
 
@@ -360,7 +359,7 @@
     public float getVisibility() {
         if (mOriginal.getVisibility() == mTarget.getVisibility()) {
             return 1f;
-        } else if (mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+        } else if (mTarget.isVisible()) {
             return mVp;
         } else {
             return 1 - mVp;
@@ -382,7 +381,7 @@
         float targetY = mTarget.getY();
         float targetW = mTarget.getW();
         float targetH = mTarget.getH();
-        Component.Visibility targetVisibility = mTarget.getVisibility();
+        int targetVisibility = mTarget.getVisibility();
         if (targetX != measure.getX()
                 || targetY != measure.getY()
                 || targetW != measure.getW()
@@ -393,7 +392,13 @@
             mTarget.setW(measure.getW());
             mTarget.setH(measure.getH());
             mTarget.setVisibility(measure.getVisibility());
-            mStartTime = currentTime;
+            // We shouldn't reset the leftover animation time here
+            // 1/ if we are eg fading out a component, and an updateTarget comes on, we don't want
+            //    to restart the full animation time
+            // 2/ if no visibility change but quick updates come in (eg live resize) it seems
+            //    better as well to not restart the animation time and only allows the original
+            //    time to wrap up
+            // mStartTime = currentTime;
         }
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
index 91348f5..c87bbdc 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
@@ -129,7 +129,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", "AnimationSpec")
+                .addType("AnimationSpec")
                 .add("motionDuration", getMotionDuration())
                 .add("motionEasingType", Easing.getString(getMotionEasingType()))
                 .add("visibilityDuration", getVisibilityDuration())
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
index 35d639e..6ee18bb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
@@ -115,8 +115,10 @@
         for (Component c : mChildrenComponents) {
             c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
             ComponentMeasure m = measure.get(c);
-            size.setWidth(Math.max(size.getWidth(), m.getW()));
-            size.setHeight(Math.max(size.getHeight(), m.getH()));
+            if (!m.isGone()) {
+                size.setWidth(Math.max(size.getWidth(), m.getW()));
+                size.setHeight(Math.max(size.getHeight(), m.getH()));
+            }
         }
         // add padding
         size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext())));
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
index 508b685..f9111df 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
@@ -154,7 +154,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         super.serialize(serializer);
-        serializer.add("type", getSerializedName());
+        serializer.addType(getSerializedName());
         serializer.add("horizontalPositioning", mHorizontalPositioning);
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
index afc41b1..b008952 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
@@ -21,6 +21,7 @@
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
@@ -134,6 +135,24 @@
     }
 
     @Override
+    public float minIntrinsicHeight(@NonNull RemoteContext context) {
+        float height = computeModifierDefinedHeight(context);
+        if (!mChildrenComponents.isEmpty()) {
+            height += mChildrenComponents.get(0).minIntrinsicHeight(context);
+        }
+        return height;
+    }
+
+    @Override
+    public float minIntrinsicWidth(@NonNull RemoteContext context) {
+        float width = computeModifierDefinedWidth(context);
+        if (!mChildrenComponents.isEmpty()) {
+            width += mChildrenComponents.get(0).minIntrinsicWidth(context);
+        }
+        return width;
+    }
+
+    @Override
     protected boolean hasVerticalIntrinsicDimension() {
         return true;
     }
@@ -147,29 +166,54 @@
             boolean verticalWrap,
             @NonNull MeasurePass measure,
             @NonNull Size size) {
-        super.computeWrapSize(
-                context, maxWidth, Float.MAX_VALUE, horizontalWrap, verticalWrap, measure, size);
-    }
-
-    @Override
-    public boolean applyVisibility(
-            float selfWidth, float selfHeight, @NonNull MeasurePass measure) {
-        float childrenWidth = 0f;
-        float childrenHeight = 0f;
-        boolean changedVisibility = false;
-        for (Component child : mChildrenComponents) {
-            ComponentMeasure childMeasure = measure.get(child);
-            if (childMeasure.getVisibility() == Visibility.GONE) {
-                continue;
-            }
-            if (childrenHeight + childMeasure.getH() > selfHeight) {
-                childMeasure.setVisibility(Visibility.GONE);
-                changedVisibility = true;
+        int visibleChildren = 0;
+        ComponentMeasure self = measure.get(this);
+        self.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE);
+        float currentMaxHeight = maxHeight;
+        for (Component c : mChildrenComponents) {
+            if (c instanceof CollapsibleColumnLayout) {
+                c.measure(context, 0f, maxWidth, 0f, currentMaxHeight, measure);
             } else {
-                childrenHeight += childMeasure.getH();
-                childrenWidth = Math.max(childrenWidth, childMeasure.getW());
+                c.measure(context, 0f, maxWidth, 0f, Float.MAX_VALUE, measure);
+            }
+            ComponentMeasure m = measure.get(c);
+            if (!m.isGone()) {
+                size.setWidth(Math.max(size.getWidth(), m.getW()));
+                size.setHeight(size.getHeight() + m.getH());
+                visibleChildren++;
+                currentMaxHeight -= m.getH();
             }
         }
-        return changedVisibility;
+        if (!mChildrenComponents.isEmpty()) {
+            size.setHeight(size.getHeight() + (mSpacedBy * (visibleChildren - 1)));
+        }
+
+        float childrenWidth = 0f;
+        float childrenHeight = 0f;
+
+        boolean overflow = false;
+        for (Component child : mChildrenComponents) {
+            ComponentMeasure childMeasure = measure.get(child);
+            if (overflow || childMeasure.isGone()) {
+                childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+                continue;
+            }
+            float childHeight = childMeasure.getH();
+            boolean childDoesNotFits = childrenHeight + childHeight > maxHeight;
+            if (childDoesNotFits) {
+                childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+                overflow = true;
+            } else {
+                childrenHeight += childHeight;
+                childrenWidth = Math.max(childrenWidth, childMeasure.getW());
+                visibleChildren++;
+            }
+        }
+        if (verticalWrap) {
+            size.setHeight(Math.min(maxHeight, childrenHeight));
+        }
+        if (visibleChildren == 0 || size.getHeight() <= 0f) {
+            self.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+        }
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
index 0e7eb86..05f3329 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
@@ -21,6 +21,7 @@
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
@@ -139,6 +140,24 @@
     }
 
     @Override
+    public float minIntrinsicWidth(@NonNull RemoteContext context) {
+        float width = computeModifierDefinedWidth(context);
+        if (!mChildrenComponents.isEmpty()) {
+            width += mChildrenComponents.get(0).minIntrinsicWidth(context);
+        }
+        return width;
+    }
+
+    @Override
+    public float minIntrinsicHeight(@NonNull RemoteContext context) {
+        float height = computeModifierDefinedHeight(context);
+        if (!mChildrenComponents.isEmpty()) {
+            height += mChildrenComponents.get(0).minIntrinsicHeight(context);
+        }
+        return height;
+    }
+
+    @Override
     public void computeWrapSize(
             @NonNull PaintContext context,
             float maxWidth,
@@ -157,19 +176,35 @@
         float childrenWidth = 0f;
         float childrenHeight = 0f;
         boolean changedVisibility = false;
+        int visibleChildren = 0;
+        ComponentMeasure self = measure.get(this);
+        self.clearVisibilityOverride();
+        if (selfWidth <= 0 || selfHeight <= 0) {
+            self.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+            return true;
+        }
         for (Component child : mChildrenComponents) {
             ComponentMeasure childMeasure = measure.get(child);
-            if (childMeasure.getVisibility() == Visibility.GONE) {
+            int visibility = childMeasure.getVisibility();
+            childMeasure.clearVisibilityOverride();
+            if (!childMeasure.isVisible()) {
                 continue;
             }
-            if (childrenWidth + childMeasure.getW() > selfWidth) {
-                childMeasure.setVisibility(Visibility.GONE);
-                changedVisibility = true;
+            if (childrenWidth + childMeasure.getW() > selfWidth
+                    && childrenHeight + childMeasure.getH() > selfHeight) {
+                childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+                if (visibility != childMeasure.getVisibility()) {
+                    changedVisibility = true;
+                }
             } else {
                 childrenWidth += childMeasure.getW();
                 childrenHeight = Math.max(childrenHeight, childMeasure.getH());
+                visibleChildren++;
             }
         }
+        if (visibleChildren == 0) {
+            self.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+        }
         return changedVisibility;
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
index 47a55b6..cda90c2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
@@ -138,7 +138,7 @@
         for (Component c : mChildrenComponents) {
             c.measure(context, 0f, maxWidth, 0f, currentMaxHeight, measure);
             ComponentMeasure m = measure.get(c);
-            if (m.getVisibility() != Visibility.GONE) {
+            if (!m.isGone()) {
                 size.setWidth(Math.max(size.getWidth(), m.getW()));
                 size.setHeight(size.getHeight() + m.getH());
                 visibleChildrens++;
@@ -164,7 +164,7 @@
         for (Component child : mChildrenComponents) {
             child.measure(context, minWidth, maxWidth, minHeight, mh, measure);
             ComponentMeasure m = measure.get(child);
-            if (m.getVisibility() != Visibility.GONE) {
+            if (!m.isGone()) {
                 mh -= m.getH();
             }
         }
@@ -172,11 +172,11 @@
     }
 
     @Override
-    public float intrinsicHeight(@NonNull RemoteContext context) {
+    public float minIntrinsicHeight(@NonNull RemoteContext context) {
         float height = computeModifierDefinedHeight(context);
         float componentHeights = 0f;
         for (Component c : mChildrenComponents) {
-            componentHeights += c.intrinsicHeight(context);
+            componentHeights += c.minIntrinsicHeight(context);
         }
         return Math.max(height, componentHeights);
     }
@@ -225,7 +225,7 @@
             float totalWeights = 0f;
             for (Component child : mChildrenComponents) {
                 ComponentMeasure childMeasure = measure.get(child);
-                if (childMeasure.getVisibility() == Visibility.GONE) {
+                if (childMeasure.isGone()) {
                     continue;
                 }
                 if (child instanceof LayoutComponent
@@ -242,7 +242,7 @@
                     if (child instanceof LayoutComponent
                             && ((LayoutComponent) child).getHeightModifier().hasWeight()) {
                         ComponentMeasure childMeasure = measure.get(child);
-                        if (childMeasure.getVisibility() == Visibility.GONE) {
+                        if (childMeasure.isGone()) {
                             continue;
                         }
                         float weight = ((LayoutComponent) child).getHeightModifier().getValue();
@@ -280,7 +280,7 @@
         int visibleChildrens = 0;
         for (Component child : mChildrenComponents) {
             ComponentMeasure childMeasure = measure.get(child);
-            if (childMeasure.getVisibility() == Visibility.GONE) {
+            if (childMeasure.isGone()) {
                 continue;
             }
             childrenWidth = Math.max(childrenWidth, childMeasure.getW());
@@ -307,17 +307,22 @@
             case SPACE_BETWEEN:
                 for (Component child : mChildrenComponents) {
                     ComponentMeasure childMeasure = measure.get(child);
-                    if (childMeasure.getVisibility() == Visibility.GONE) {
+                    if (childMeasure.isGone()) {
                         continue;
                     }
                     total += childMeasure.getH();
                 }
-                verticalGap = (selfHeight - total) / (visibleChildrens - 1);
+                if (visibleChildrens > 1) {
+                    verticalGap = (selfHeight - total) / (visibleChildrens - 1);
+                } else {
+                    // we center the element
+                    ty = (selfHeight - childrenHeight) / 2f;
+                }
                 break;
             case SPACE_EVENLY:
                 for (Component child : mChildrenComponents) {
                     ComponentMeasure childMeasure = measure.get(child);
-                    if (childMeasure.getVisibility() == Visibility.GONE) {
+                    if (childMeasure.isGone()) {
                         continue;
                     }
                     total += childMeasure.getH();
@@ -328,7 +333,7 @@
             case SPACE_AROUND:
                 for (Component child : mChildrenComponents) {
                     ComponentMeasure childMeasure = measure.get(child);
-                    if (childMeasure.getVisibility() == Visibility.GONE) {
+                    if (childMeasure.isGone()) {
                         continue;
                     }
                     total += childMeasure.getH();
@@ -353,7 +358,7 @@
             }
             childMeasure.setX(tx);
             childMeasure.setY(ty);
-            if (childMeasure.getVisibility() == Visibility.GONE) {
+            if (childMeasure.isGone()) {
                 continue;
             }
             ty += childMeasure.getH();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java
new file mode 100644
index 0000000..ff7a3af
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.managers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+
+import java.util.List;
+
+/** FitBox layout implementation -- only display the child that fits in the available space */
+public class FitBoxLayout extends LayoutManager {
+
+    public static final int START = 1;
+    public static final int CENTER = 2;
+    public static final int END = 3;
+    public static final int TOP = 4;
+    public static final int BOTTOM = 5;
+
+    int mHorizontalPositioning;
+    int mVerticalPositioning;
+
+    public FitBoxLayout(
+            @Nullable Component parent,
+            int componentId,
+            int animationId,
+            float x,
+            float y,
+            float width,
+            float height,
+            int horizontalPositioning,
+            int verticalPositioning) {
+        super(parent, componentId, animationId, x, y, width, height);
+        mHorizontalPositioning = horizontalPositioning;
+        mVerticalPositioning = verticalPositioning;
+    }
+
+    public FitBoxLayout(
+            @Nullable Component parent,
+            int componentId,
+            int animationId,
+            int horizontalPositioning,
+            int verticalPositioning) {
+        this(
+                parent,
+                componentId,
+                animationId,
+                0,
+                0,
+                0,
+                0,
+                horizontalPositioning,
+                verticalPositioning);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "BOX ["
+                + mComponentId
+                + ":"
+                + mAnimationId
+                + "] ("
+                + mX
+                + ", "
+                + mY
+                + " - "
+                + mWidth
+                + " x "
+                + mHeight
+                + ") "
+                + mVisibility;
+    }
+
+    @NonNull
+    @Override
+    protected String getSerializedName() {
+        return "FITBOX";
+    }
+
+    @Override
+    public void computeWrapSize(
+            @NonNull PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            boolean horizontalWrap,
+            boolean verticalWrap,
+            @NonNull MeasurePass measure,
+            @NonNull Size size) {
+
+        boolean found = false;
+        ComponentMeasure self = measure.get(this);
+        for (Component c : mChildrenComponents) {
+            float cw = 0f; // c.intrinsicWidth(context.getContext());
+            float ch = 0f; // c.intrinsicHeight(context.getContext());
+            if (c instanceof LayoutComponent) {
+                LayoutComponent lc = (LayoutComponent) c;
+                WidthModifierOperation widthModifier = lc.getWidthModifier();
+                if (widthModifier != null) {
+                    WidthInModifierOperation widthIn = lc.getWidthModifier().getWidthIn();
+                    if (widthIn != null) {
+                        cw = widthIn.getMin();
+                    }
+                }
+                HeightModifierOperation heightModifier = lc.getHeightModifier();
+                if (heightModifier != null) {
+                    HeightInModifierOperation heightIn = lc.getHeightModifier().getHeightIn();
+                    if (heightIn != null) {
+                        ch = heightIn.getMin();
+                    }
+                }
+            }
+            c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
+            ComponentMeasure m = measure.get(c);
+            if (!found && cw <= maxWidth && ch <= maxHeight) {
+                found = true;
+                m.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE);
+                size.setWidth(m.getW());
+                size.setHeight(m.getH());
+            } else {
+                m.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+            }
+        }
+        if (!found) {
+            self.setVisibility(Visibility.GONE);
+        } else {
+            self.setVisibility(Visibility.VISIBLE);
+        }
+
+        // add padding
+        size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext())));
+        size.setHeight(
+                Math.max(size.getHeight(), computeModifierDefinedHeight(context.getContext())));
+    }
+
+    @Override
+    public void computeSize(
+            @NonNull PaintContext context,
+            float minWidth,
+            float maxWidth,
+            float minHeight,
+            float maxHeight,
+            @NonNull MeasurePass measure) {
+
+        ComponentMeasure self = measure.get(this);
+        boolean found = false;
+        for (Component c : mChildrenComponents) {
+            float cw = 0f;
+            float ch = 0f;
+            if (c instanceof LayoutComponent) {
+                LayoutComponent lc = (LayoutComponent) c;
+                WidthModifierOperation widthModifier = lc.getWidthModifier();
+                if (widthModifier != null) {
+                    WidthInModifierOperation widthIn = lc.getWidthModifier().getWidthIn();
+                    if (widthIn != null) {
+                        cw = widthIn.getMin();
+                    }
+                }
+                HeightModifierOperation heightModifier = lc.getHeightModifier();
+                if (heightModifier != null) {
+                    HeightInModifierOperation heightIn = lc.getHeightModifier().getHeightIn();
+                    if (heightIn != null) {
+                        ch = heightIn.getMin();
+                    }
+                }
+            }
+            c.measure(context, minWidth, maxWidth, minHeight, maxHeight, measure);
+            //                child.measure(context, minWidth, Float.MAX_VALUE, minHeight,
+            // Float.MAX_VALUE, measure);
+            //               m.getVisibility().clearOverride();
+            ComponentMeasure m = measure.get(c);
+            //                m.setVisibility(Visibility.GONE);
+            //                m.getVisibility().add(Visibility.OVERRIDE_GONE);
+            // m.getVisibility().add(Visibility.OVERRIDE_GONE);
+            m.clearVisibilityOverride();
+            if (!found && cw <= maxWidth && ch <= maxHeight) {
+                found = true;
+                m.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE);
+            } else {
+                m.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+            }
+        }
+    }
+
+    @Override
+    public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
+        ComponentMeasure selfMeasure = measure.get(this);
+        float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
+        float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
+        applyVisibility(selfWidth, selfHeight, measure);
+        for (Component child : mChildrenComponents) {
+            ComponentMeasure m = measure.get(child);
+            float tx = 0f;
+            float ty = 0f;
+            switch (mVerticalPositioning) {
+                case TOP:
+                    ty = 0f;
+                    break;
+                case CENTER:
+                    ty = (selfHeight - m.getH()) / 2f;
+                    break;
+                case BOTTOM:
+                    ty = selfHeight - m.getH();
+                    break;
+            }
+            switch (mHorizontalPositioning) {
+                case START:
+                    tx = 0f;
+                    break;
+                case CENTER:
+                    tx = (selfWidth - m.getW()) / 2f;
+                    break;
+                case END:
+                    tx = selfWidth - m.getW();
+                    break;
+            }
+            m.setX(tx);
+            m.setY(ty);
+        }
+    }
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
+    public static String name() {
+        return "BoxLayout";
+    }
+
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
+    public static int id() {
+        return Operations.LAYOUT_FIT_BOX;
+    }
+
+    /**
+     * Write the operation to the buffer
+     *
+     * @param buffer a WireBuffer
+     * @param componentId the component id
+     * @param animationId the component animation id
+     * @param horizontalPositioning the horizontal positioning rules
+     * @param verticalPositioning the vertical positioning rules
+     */
+    public static void apply(
+            @NonNull WireBuffer buffer,
+            int componentId,
+            int animationId,
+            int horizontalPositioning,
+            int verticalPositioning) {
+        buffer.start(id());
+        buffer.writeInt(componentId);
+        buffer.writeInt(animationId);
+        buffer.writeInt(horizontalPositioning);
+        buffer.writeInt(verticalPositioning);
+    }
+
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+        int componentId = buffer.readInt();
+        int animationId = buffer.readInt();
+        int horizontalPositioning = buffer.readInt();
+        int verticalPositioning = buffer.readInt();
+        operations.add(
+                new FitBoxLayout(
+                        null,
+                        componentId,
+                        animationId,
+                        horizontalPositioning,
+                        verticalPositioning));
+    }
+
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
+    public static void documentation(@NonNull DocumentationBuilder doc) {
+        doc.operation("Layout Operations", id(), name())
+                .description(
+                        "FitBox layout implementation.\n\n"
+                            + "Only display the first child component that fits in the available"
+                            + " space")
+                .examplesDimension(150, 100)
+                .exampleImage("Top", "layout-BoxLayout-start-top.png")
+                .exampleImage("Center", "layout-BoxLayout-center-center.png")
+                .exampleImage("Bottom", "layout-BoxLayout-end-bottom.png")
+                .field(INT, "COMPONENT_ID", "unique id for this component")
+                .field(
+                        INT,
+                        "ANIMATION_ID",
+                        "id used to match components," + " for animation purposes")
+                .field(INT, "HORIZONTAL_POSITIONING", "horizontal positioning value")
+                .possibleValues("START", FitBoxLayout.START)
+                .possibleValues("CENTER", FitBoxLayout.CENTER)
+                .possibleValues("END", FitBoxLayout.END)
+                .field(INT, "VERTICAL_POSITIONING", "vertical positioning value")
+                .possibleValues("TOP", FitBoxLayout.TOP)
+                .possibleValues("CENTER", FitBoxLayout.CENTER)
+                .possibleValues("BOTTOM", FitBoxLayout.BOTTOM);
+    }
+
+    @Override
+    public void write(@NonNull WireBuffer buffer) {
+        apply(buffer, mComponentId, mAnimationId, mHorizontalPositioning, mVerticalPositioning);
+    }
+
+    @Override
+    public void serialize(MapSerializer serializer) {
+        super.serialize(serializer);
+        serializer.add("verticalPositioning", getPositioningString(mVerticalPositioning));
+        serializer.add("horizontalPositioning", getPositioningString(mHorizontalPositioning));
+    }
+
+    private String getPositioningString(int pos) {
+        switch (pos) {
+            case START:
+                return "START";
+            case CENTER:
+                return "CENTER";
+            case END:
+                return "END";
+            case TOP:
+                return "TOP";
+            case BOTTOM:
+                return "BOTTOM";
+            default:
+                return "NONE";
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
index 8b52bbe..5b66b95 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
@@ -73,19 +73,19 @@
     }
 
     @Override
-    public float intrinsicHeight(@Nullable RemoteContext context) {
+    public float minIntrinsicHeight(@Nullable RemoteContext context) {
         float height = computeModifierDefinedHeight(context);
         for (Component c : mChildrenComponents) {
-            height = Math.max(c.intrinsicHeight(context), height);
+            height = Math.max(c.minIntrinsicHeight(context), height);
         }
         return height;
     }
 
     @Override
-    public float intrinsicWidth(@Nullable RemoteContext context) {
+    public float minIntrinsicWidth(@Nullable RemoteContext context) {
         float width = computeModifierDefinedWidth(context);
         for (Component c : mChildrenComponents) {
-            width = Math.max(c.intrinsicWidth(context), width);
+            width = Math.max(c.minIntrinsicWidth(context), width);
         }
         return width;
     }
@@ -149,10 +149,10 @@
                 Math.min(maxHeight, computeModifierDefinedHeight(context.getContext()));
 
         if (mWidthModifier.isIntrinsicMin()) {
-            maxWidth = intrinsicWidth(context.getContext()) + mPaddingLeft + mPaddingRight;
+            maxWidth = minIntrinsicWidth(context.getContext()) + mPaddingLeft + mPaddingRight;
         }
         if (mHeightModifier.isIntrinsicMin()) {
-            maxHeight = intrinsicHeight(context.getContext()) + mPaddingTop + mPaddingBottom;
+            maxHeight = minIntrinsicHeight(context.getContext()) + mPaddingTop + mPaddingBottom;
         }
 
         float insetMaxWidth = maxWidth - mPaddingLeft - mPaddingRight;
@@ -171,6 +171,11 @@
                     mHeightModifier.isWrap(),
                     measure,
                     mCachedWrapSize);
+            int selfVisibilityAfterMeasure = measure.get(this).getVisibility();
+            if (Visibility.hasOverride(selfVisibilityAfterMeasure)
+                    && mScheduledVisibility != selfVisibilityAfterMeasure) {
+                mScheduledVisibility = selfVisibilityAfterMeasure;
+            }
             measuredWidth = mCachedWrapSize.getWidth();
             if (hasHorizontalWrap) {
                 measuredWidth += mPaddingLeft + mPaddingRight;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
index e93cbd7..d5d2e03 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
@@ -136,7 +136,7 @@
         for (Component c : mChildrenComponents) {
             c.measure(context, 0f, currentMaxWidth, 0f, maxHeight, measure);
             ComponentMeasure m = measure.get(c);
-            if (m.getVisibility() != Visibility.GONE) {
+            if (!m.isGone()) {
                 size.setWidth(size.getWidth() + m.getW());
                 size.setHeight(Math.max(size.getHeight(), m.getH()));
                 visibleChildrens++;
@@ -162,7 +162,7 @@
         for (Component child : mChildrenComponents) {
             child.measure(context, minWidth, mw, minHeight, maxHeight, measure);
             ComponentMeasure m = measure.get(child);
-            if (m.getVisibility() != Visibility.GONE) {
+            if (!m.isGone()) {
                 mw -= m.getW();
             }
         }
@@ -170,16 +170,26 @@
     }
 
     @Override
-    public float intrinsicWidth(@Nullable RemoteContext context) {
+    public float minIntrinsicWidth(@Nullable RemoteContext context) {
         float width = computeModifierDefinedWidth(context);
         float componentWidths = 0f;
         for (Component c : mChildrenComponents) {
-            componentWidths += c.intrinsicWidth(context);
+            componentWidths += c.minIntrinsicWidth(context);
         }
         return Math.max(width, componentWidths);
     }
 
     @Override
+    public float minIntrinsicHeight(@Nullable RemoteContext context) {
+        float height = computeModifierDefinedHeight(context);
+        float componentHeights = 0f;
+        for (Component c : mChildrenComponents) {
+            componentHeights = Math.max(componentHeights, c.minIntrinsicHeight(context));
+        }
+        return Math.max(height, componentHeights);
+    }
+
+    @Override
     public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         DebugLog.s(
@@ -225,7 +235,7 @@
             float totalWeights = 0f;
             for (Component child : mChildrenComponents) {
                 ComponentMeasure childMeasure = measure.get(child);
-                if (childMeasure.getVisibility() == Visibility.GONE) {
+                if (childMeasure.isGone()) {
                     continue;
                 }
                 if (child instanceof LayoutComponent
@@ -245,7 +255,7 @@
                     if (child instanceof LayoutComponent
                             && ((LayoutComponent) child).getWidthModifier().hasWeight()) {
                         ComponentMeasure childMeasure = measure.get(child);
-                        if (childMeasure.getVisibility() == Visibility.GONE) {
+                        if (childMeasure.isGone()) {
                             continue;
                         }
                         float weight = ((LayoutComponent) child).getWidthModifier().getValue();
@@ -283,7 +293,7 @@
         int visibleChildrens = 0;
         for (Component child : mChildrenComponents) {
             ComponentMeasure childMeasure = measure.get(child);
-            if (childMeasure.getVisibility() == Visibility.GONE) {
+            if (childMeasure.isGone()) {
                 continue;
             }
             childrenWidth += childMeasure.getW();
@@ -311,17 +321,22 @@
             case SPACE_BETWEEN:
                 for (Component child : mChildrenComponents) {
                     ComponentMeasure childMeasure = measure.get(child);
-                    if (childMeasure.getVisibility() == Visibility.GONE) {
+                    if (childMeasure.isGone()) {
                         continue;
                     }
                     total += childMeasure.getW();
                 }
-                horizontalGap = (selfWidth - total) / (visibleChildrens - 1);
+                if (visibleChildrens > 1) {
+                    horizontalGap = (selfWidth - total) / (visibleChildrens - 1);
+                } else {
+                    // we center the element
+                    tx = (selfWidth - childrenWidth) / 2f;
+                }
                 break;
             case SPACE_EVENLY:
                 for (Component child : mChildrenComponents) {
                     ComponentMeasure childMeasure = measure.get(child);
-                    if (childMeasure.getVisibility() == Visibility.GONE) {
+                    if (childMeasure.isGone()) {
                         continue;
                     }
                     total += childMeasure.getW();
@@ -332,7 +347,7 @@
             case SPACE_AROUND:
                 for (Component child : mChildrenComponents) {
                     ComponentMeasure childMeasure = measure.get(child);
-                    if (childMeasure.getVisibility() == Visibility.GONE) {
+                    if (childMeasure.isGone()) {
                         continue;
                     }
                     total += childMeasure.getW();
@@ -357,7 +372,7 @@
             }
             childMeasure.setX(tx);
             childMeasure.setY(ty);
-            if (childMeasure.getVisibility() == Visibility.GONE) {
+            if (childMeasure.isGone()) {
                 continue;
             }
             tx += childMeasure.getW();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
index ee16bc2..0192d84 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
@@ -413,7 +413,7 @@
                 if (index != currentLayoutIndex && index != previousLayoutIndex) {
                     pane.mVisibility = Visibility.GONE;
                 }
-                if (index == currentLayoutIndex && pane.mVisibility != Visibility.VISIBLE) {
+                if (index == currentLayoutIndex && !pane.isVisible()) {
                     pane.mVisibility = Visibility.VISIBLE;
                 }
                 index++;
@@ -511,7 +511,7 @@
                 && previousLayout.mAnimateMeasure == null) {
             inTransition = false;
             LayoutManager previous = getLayout(previousLayoutIndex);
-            if (previous != currentLayout && previous.mVisibility != Visibility.GONE) {
+            if (previous != currentLayout && !previous.isGone()) {
                 previous.mVisibility = Visibility.GONE;
                 previous.needsRepaint();
             }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
index d5db74b..2595a71 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
@@ -280,7 +280,7 @@
                 + " x "
                 + mHeight
                 + ") "
-                + mVisibility;
+                + Visibility.toString(mVisibility);
     }
 
     @NonNull
@@ -308,7 +308,7 @@
                         + ", "
                         + mHeight
                         + "] "
-                        + mVisibility
+                        + Visibility.toString(mVisibility)
                         + " ("
                         + mTextId
                         + ":\""
@@ -343,7 +343,7 @@
             flags |= PaintContext.TEXT_COMPLEX;
         }
         context.getTextBounds(mTextId, 0, mCachedString.length(), flags, bounds);
-        if (bounds[2] - bounds[1] > maxWidth && mMaxLines > 1) {
+        if (bounds[2] - bounds[1] > maxWidth && mMaxLines > 1 && maxWidth > 0f) {
             mComputedTextLayout =
                     context.layoutComplexText(
                             mTextId,
@@ -375,12 +375,12 @@
     }
 
     @Override
-    public float intrinsicHeight(@Nullable RemoteContext context) {
+    public float minIntrinsicHeight(@Nullable RemoteContext context) {
         return mTextH;
     }
 
     @Override
-    public float intrinsicWidth(@Nullable RemoteContext context) {
+    public float minIntrinsicWidth(@Nullable RemoteContext context) {
         return mTextW;
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
index 11ed9f4..9934419 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
@@ -26,7 +26,7 @@
     float mY;
     float mW;
     float mH;
-    @NonNull Component.Visibility mVisibility = Component.Visibility.VISIBLE;
+    int mVisibility = Component.Visibility.VISIBLE;
 
     public void setX(float value) {
         mX = value;
@@ -60,16 +60,15 @@
         return mH;
     }
 
-    public @NonNull Component.Visibility getVisibility() {
+    public int getVisibility() {
         return mVisibility;
     }
 
-    public void setVisibility(@NonNull Component.Visibility visibility) {
+    public void setVisibility(int visibility) {
         mVisibility = visibility;
     }
 
-    public ComponentMeasure(
-            int id, float x, float y, float w, float h, @NonNull Component.Visibility visibility) {
+    public ComponentMeasure(int id, float x, float y, float w, float h, int visibility) {
         this.mId = id;
         this.mX = x;
         this.mY = y;
@@ -114,4 +113,42 @@
     public boolean same(@NonNull ComponentMeasure m) {
         return mX == m.mX && mY == m.mY && mW == m.mW && mH == m.mH && mVisibility == m.mVisibility;
     }
+
+    /**
+     * Returns true if the component will be gone
+     *
+     * @return true if gone
+     */
+    public boolean isGone() {
+        return Component.Visibility.isGone(mVisibility);
+    }
+
+    /**
+     * Returns true if the component will be visible
+     *
+     * @return true if visible
+     */
+    public boolean isVisible() {
+        return Component.Visibility.isVisible(mVisibility);
+    }
+
+    /**
+     * Returns true if the component will be invisible
+     *
+     * @return true if invisible
+     */
+    public boolean isInvisible() {
+        return Component.Visibility.isInvisible(mVisibility);
+    }
+
+    /** Clear any override on the visibility */
+    public void clearVisibilityOverride() {
+        mVisibility = Component.Visibility.clearOverride(mVisibility);
+    }
+
+    /** Add a visibility override */
+    public void addVisibilityOverride(int value) {
+        mVisibility = Component.Visibility.clearOverride(mVisibility);
+        mVisibility = Component.Visibility.add(mVisibility, value);
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
index fd5f8c9..1ab0c3d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
@@ -228,7 +228,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "BackgroundModifierOperation")
+                .addType("BackgroundModifierOperation")
                 .add("x", mX)
                 .add("y", mY)
                 .add("width", mWidth)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
index e5f3183..656a3c0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
@@ -290,7 +290,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "BorderModifierOperation")
+                .addType("BorderModifierOperation")
                 .add("x", mX)
                 .add("y", mY)
                 .add("width", mWidth)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
index 00a5317..e96dc83 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
@@ -111,7 +111,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "ClipRectModifierOperation")
+                .addType("ClipRectModifierOperation")
                 .add("width", mWidth)
                 .add("height", mHeight);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
index a9e3421..14b2fad 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
@@ -327,7 +327,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "ComponentModifiers")
+                .addType("ComponentModifiers")
                 .add("modifiers", mList);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
index fbf8a95..88b28c3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
@@ -41,7 +41,7 @@
     private static final int OP_CODE = Operations.MODIFIER_VISIBILITY;
 
     int mVisibilityId;
-    @NonNull Component.Visibility mVisibility = Component.Visibility.VISIBLE;
+    int mVisibility = Component.Visibility.VISIBLE;
     private LayoutComponent mParent;
 
     public ComponentVisibilityOperation(int id) {
@@ -124,11 +124,11 @@
     @Override
     public void updateVariables(@NonNull RemoteContext context) {
         int visibility = context.getInteger(mVisibilityId);
-        if (visibility == Component.Visibility.VISIBLE.ordinal()) {
+        if (Component.Visibility.isVisible(visibility)) {
             mVisibility = Component.Visibility.VISIBLE;
-        } else if (visibility == Component.Visibility.GONE.ordinal()) {
+        } else if (Component.Visibility.isGone(visibility)) {
             mVisibility = Component.Visibility.GONE;
-        } else if (visibility == Component.Visibility.INVISIBLE.ordinal()) {
+        } else if (Component.Visibility.isInvisible(visibility)) {
             mVisibility = Component.Visibility.INVISIBLE;
         } else {
             mVisibility = Component.Visibility.GONE;
@@ -150,8 +150,8 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "ComponentVisibilityOperation")
+                .addType("ComponentVisibilityOperation")
                 .add("visibilityId", mVisibilityId)
-                .add("visibility", mVisibility);
+                .add("visibility", Component.Visibility.toString(mVisibility));
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java
index d7abdba..6beb135 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java
@@ -120,6 +120,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.addTags(SerializeTags.MODIFIER).add("type", "DrawContentOperation");
+        serializer.addTags(SerializeTags.MODIFIER).addType("DrawContentOperation");
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
index c1c1f95..361438b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
@@ -352,7 +352,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "GraphicsLayerModifierOperation")
+                .addType("GraphicsLayerModifierOperation")
                 .add("scaleX", mScaleX)
                 .add("scaleX", mScaleX)
                 .add("rotationX", mRotationX)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
index 7f0dd8d..9b63c03 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
@@ -107,7 +107,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "HeightInModifierOperation")
+                .addType("HeightInModifierOperation")
                 .add("min", mV1, mValue1)
                 .add("max", mV2, mValue2);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
index 1df8425..5fbaafc 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
@@ -143,7 +143,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "HeightModifierOperation")
+                .addType("HeightModifierOperation")
                 .add("height", mValue, mOutValue)
                 .add("dimensionModifierType", mType);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
index 67714ef..4d8acb4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
@@ -130,7 +130,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "HostActionOperation")
+                .addType("HostActionOperation")
                 .add("id", mActionId);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
index 40c13f14..807ff68 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
@@ -157,7 +157,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "HostNamedActionOperation")
+                .addType("HostNamedActionOperation")
                 .add("textId", mTextId)
                 .add("actionType", getActionType(mType))
                 .add("valueId", mValueId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
index d2a1684..c493e25 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
@@ -247,8 +247,8 @@
         mComponentHeight = height;
         if (component instanceof LayoutComponent) {
             LayoutComponent layoutComponent = (LayoutComponent) component;
-            setContentWidth(layoutComponent.intrinsicWidth(context));
-            setContentHeight(layoutComponent.intrinsicHeight(context));
+            setContentWidth(layoutComponent.minIntrinsicWidth(context));
+            setContentHeight(layoutComponent.minIntrinsicHeight(context));
         }
     }
 
@@ -256,7 +256,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "MarqueeModifierOperation")
+                .addType("MarqueeModifierOperation")
                 .add("iterations", mIterations)
                 .add("animationMode", mAnimationMode)
                 .add("repeatDelayMillis", mRepeatDelayMillis)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
index b7fe97b..37f56cb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
@@ -162,7 +162,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "OffsetModifierOperation")
+                .addType("OffsetModifierOperation")
                 .add("x", mX)
                 .add("y", mY);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
index d5b3a0b..0156992 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
@@ -184,7 +184,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "PaddingModifierOperation")
+                .addType("PaddingModifierOperation")
                 .add("left", mLeft)
                 .add("top", mTop)
                 .add("right", mRight)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
index 69ace84..eb5bfcf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
@@ -217,7 +217,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "RippleModifierOperation")
+                .addType("RippleModifierOperation")
                 .add("animateRippleStart", mAnimateRippleStart)
                 .add("animateRippleX", mAnimateRippleX)
                 .add("animateRippleY", mAnimateRippleY)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
index 8442e05..f0ed905 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
@@ -167,7 +167,7 @@
     public void serialize(MapSerializer serializer) {
         serialize(serializer, "topStart", "topEnd", "bottomStart", "bottomEnd")
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", CLASS_NAME)
+                .addType(CLASS_NAME)
                 .add("width", mWidth)
                 .add("height", mHeight);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
index a57365e..466e435e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
@@ -397,7 +397,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "ScrollModifierOperation")
+                .addType("ScrollModifierOperation")
                 .add("direction", mDirection)
                 .add("max", mMax)
                 .add("notchMax", mNotchMax)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
index bd91734..171e2be 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
@@ -126,7 +126,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
-                .add("type", "ValueFloatChangeActionOperation")
+                .addType("ValueFloatChangeActionOperation")
                 .add("targetValueId", mTargetValueId)
                 .add("value", mValue);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
index 4b18d0a..d8133f6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
@@ -133,7 +133,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
-                .add("type", "ValueFloatExpressionChangeActionOperation")
+                .addType("ValueFloatExpressionChangeActionOperation")
                 .add("targetValueId", mTargetValueId)
                 .add("valueExpressionId", mValueExpressionId);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
index d86c4a6..05a6fd0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
@@ -131,7 +131,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
-                .add("type", "ValueIntegerChangeActionOperation")
+                .addType("ValueIntegerChangeActionOperation")
                 .add("targetValueId", mTargetValueId)
                 .add("value", mValue);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
index e253460..8994feb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
@@ -133,7 +133,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
-                .add("type", "ValueIntegerExpressionChangeActionOperation")
+                .addType("ValueIntegerExpressionChangeActionOperation")
                 .add("targetValueId", mTargetValueId)
                 .add("valueExpressionId", mValueExpressionId);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
index e84b299..08960d3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
@@ -139,7 +139,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
-                .add("type", "ValueIntegerExpressionChangeActionOperation")
+                .addType("ValueIntegerExpressionChangeActionOperation")
                 .add("targetValueId", mTargetValueId)
                 .add("valueId", mValueId);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
index 3282a9c..93074c7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
@@ -107,7 +107,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "WidthInModifierOperation")
+                .addType("WidthInModifierOperation")
                 .add("min", mV1, mValue1)
                 .add("max", mV2, mValue2);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
index 6fe5a70..ebdafa2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
@@ -143,7 +143,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "WidthModifierOperation")
+                .addType("WidthModifierOperation")
                 .add("width", mValue, mOutValue)
                 .add("dimensionModifierType", mType);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
index f250951..ddb34b5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
@@ -147,7 +147,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER)
-                .add("type", "ZIndexModifierOperation")
+                .addType("ZIndexModifierOperation")
                 .add("value", mValue);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
index 0f17b11..55b6436 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
@@ -1248,7 +1248,7 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", "PaintBundle");
+        serializer.addType("PaintBundle");
         List<Map<String, Object>> list = new ArrayList<>();
         int i = 0;
         while (i < mPos) {
@@ -1336,6 +1336,7 @@
         serializer.add("operations", list);
     }
 
+    @SuppressWarnings("JdkImmutableCollections")
     private static Map<String, Object> getVariable(int value) {
         float fValue = Float.intBitsToFloat(value);
         if (Float.isNaN(fValue)) {
@@ -1344,6 +1345,7 @@
         return orderedOf("type", "Value", "value", fValue);
     }
 
+    @SuppressWarnings("JdkImmutableCollections")
     private static int serializeGradient(
             int cmd, int[] array, int i, List<Map<String, Object>> list) {
         int ret = i;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
index cad7605..349ab61 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
@@ -34,8 +34,10 @@
     private float mWrap = Float.NaN;
     private float mInitialValue = Float.NaN;
     private float mTargetValue = Float.NaN;
+    private int mDirectionalSnap = 0;
     //    private float mScale = 1;
     float mOffset = 0;
+    private boolean mPropagate = false;
 
     @NonNull
     @Override
@@ -161,11 +163,15 @@
         int type = 0;
         float wrapValue = Float.NaN;
         float initialValue = Float.NaN;
+        int directionalSnap = 0;
+        boolean propagate = false;
         if (mSpec.length > 1) {
             int num_type = Float.floatToRawIntBits(mSpec[1]);
             type = num_type & 0xFF;
             boolean wrap = ((num_type >> 8) & 0x1) > 0;
             boolean init = ((num_type >> 8) & 0x2) > 0;
+            directionalSnap = (num_type >> 10) & 0x3;
+            propagate = ((num_type >> 12) & 0x1) > 0;
             len = (num_type >> 16) & 0xFFFF;
             int off = 2 + len;
             if (init) {
@@ -229,6 +235,12 @@
         if (!Float.isNaN(wrapValue)) {
             str += " wrap =" + wrapValue;
         }
+        if (directionalSnap != 0) {
+            str += " directionalSnap=" + directionalSnap;
+        }
+        if (propagate) {
+            str += " propagate";
+        }
         return str;
     }
 
@@ -246,6 +258,8 @@
             mType = num_type & 0xFF;
             boolean wrap = ((num_type >> 8) & 0x1) > 0;
             boolean init = ((num_type >> 8) & 0x2) > 0;
+            int directional = (num_type >> 10) & 0x3;
+            boolean propagate = ((num_type >> 12) & 0x1) > 0;
             len = (num_type >> 16) & 0xFFFF;
             int off = 2 + len;
             if (init) {
@@ -254,6 +268,8 @@
             if (wrap) {
                 mWrap = mSpec[off];
             }
+            mDirectionalSnap = directional;
+            mPropagate = propagate;
         }
         create(mType, description, 2, len);
     }
@@ -347,7 +363,13 @@
             float dist = wrapDistance(mWrap, mInitialValue, mTargetValue);
             if ((dist > 0) && (mTargetValue < mInitialValue)) {
                 mTargetValue += mWrap;
-            } else if ((dist < 0) && (mTargetValue > mInitialValue)) {
+            } else if ((dist < 0) && mDirectionalSnap != 0) {
+                if (mDirectionalSnap == 1 && mTargetValue > mInitialValue) {
+                    mInitialValue = mTargetValue;
+                }
+                if (mDirectionalSnap == 2 && mTargetValue < mInitialValue) {
+                    mInitialValue = mTargetValue;
+                }
                 mTargetValue -= mWrap;
             }
         }
@@ -377,6 +399,14 @@
     /** get the value at time t in seconds since start */
     @Override
     public float get(float t) {
+        if (mDirectionalSnap == 1 && mTargetValue < mInitialValue) {
+            mInitialValue = mTargetValue;
+            return mTargetValue;
+        }
+        if (mDirectionalSnap == 2 && mTargetValue > mInitialValue) {
+            mInitialValue = mTargetValue;
+            return mTargetValue;
+        }
         return mEasingCurve.get(t / mDuration) * (mTargetValue - mInitialValue) + mInitialValue;
     }
 
@@ -387,6 +417,13 @@
     }
 
     /**
+     * @return if you should propagate the animation
+     */
+    public boolean isPropagate() {
+        return mPropagate;
+    }
+
+    /**
      * Get the initial value
      *
      * @return the initial value
@@ -398,7 +435,7 @@
     @Override
     public void serialize(MapSerializer serializer) {
         serializer
-                .add("type", "FloatAnimation")
+                .addType("FloatAnimation")
                 .add("initialValue", mInitialValue)
                 .add("targetValue", mInitialValue)
                 .add("duration", mInitialValue)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
index 08559fc..424894a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
@@ -161,7 +161,7 @@
     public void serialize(MapSerializer serializer) {
         serializer
                 .addTags(SerializeTags.MODIFIER, SerializeTags.A11Y)
-                .add("type", "CoreSemantics")
+                .addType("CoreSemantics")
                 .add("contentDescriptionId", mContentDescriptionId)
                 .add("role", mRole)
                 .add("textId", mTextId)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
index f9ecf0f..20e94ab 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
@@ -25,11 +25,17 @@
 public interface MapSerializer {
 
     /**
+     * Adds a "type" field with this value
+     *
+     * @param type The name of the type
+     */
+    MapSerializer addType(String type);
+
+    /**
      * Add a float expression
      *
-     * @param key
-     * @param value
-     * @return
+     * @param key The key
+     * @param value The float src
      */
     MapSerializer addFloatExpressionSrc(String key, float[] value);
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
index cb759a6..0da543f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
@@ -131,6 +131,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+        serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
index c734f81..bdc7659 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
@@ -123,6 +123,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+        serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
index 50509f3..d071e0a2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
@@ -35,9 +35,13 @@
     private static final String CLASS_NAME = "LongConstant";
 
     private static final int OP_CODE = Operations.DATA_LONG;
-    private final long mValue;
+    private long mValue;
     private final int mId;
 
+    /**
+     * @param id the id of the constant
+     * @param value the value of the constant
+     */
     public LongConstant(int id, long value) {
         mId = id;
         mValue = value;
@@ -52,6 +56,15 @@
         return mValue;
     }
 
+    /**
+     * Set the value of the long constant
+     *
+     * @param value the value to set it to
+     */
+    public void setValue(long value) {
+        mValue = value;
+    }
+
     @Override
     public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mValue);
@@ -114,6 +127,6 @@
 
     @Override
     public void serialize(MapSerializer serializer) {
-        serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+        serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
index 1d1e579..1f9a274 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -388,6 +388,16 @@
         mInner.setColor(colorName, colorValue);
     }
 
+    /**
+     * This sets long based on its name.
+     *
+     * @param name Name of the color
+     * @param value The new long value
+     */
+    public void setLong(String name, long value) {
+        mInner.setLong(name, value);
+    }
+
     private void mapColors() {
         String[] name = getNamedColors();
 
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
index ac4a294..b5aedd8 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -290,8 +290,8 @@
         }
 
         if ((flags & PaintContext.TEXT_MEASURE_FONT_HEIGHT) != 0) {
-            bounds[1] = Math.round(mCachedFontMetrics.top);
-            bounds[3] = Math.round(mCachedFontMetrics.bottom);
+            bounds[1] = Math.round(mCachedFontMetrics.ascent);
+            bounds[3] = Math.round(mCachedFontMetrics.descent);
         } else {
             bounds[1] = mTmpRect.top;
             bounds[3] = mTmpRect.bottom;
@@ -344,6 +344,7 @@
             default:
         }
         staticLayoutBuilder.setMaxLines(maxLines);
+        staticLayoutBuilder.setIncludePad(false);
 
         StaticLayout staticLayout = staticLayoutBuilder.build();
         return new AndroidComputedTextLayout(
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
index ba8d83b..51c42fe 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
@@ -60,6 +60,14 @@
     }
 
     @Override
+    public boolean isAlpha8Image(@NonNull Object image) {
+        if (image instanceof Bitmap) {
+            return ((Bitmap) image).getConfig().equals(Bitmap.Config.ALPHA_8);
+        }
+        return false;
+    }
+
+    @Override
     @Nullable
     public float[] pathToFloatArray(@NonNull Object path) {
         //        if (path is RemotePath) {
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
index 14349b0..b31c760 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -20,6 +20,7 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -30,6 +31,7 @@
 import com.android.internal.widget.remotecompose.core.operations.ShaderData;
 import com.android.internal.widget.remotecompose.core.operations.utilities.ArrayAccess;
 import com.android.internal.widget.remotecompose.core.operations.utilities.DataMap;
+import com.android.internal.widget.remotecompose.core.types.LongConstant;
 
 import java.io.IOException;
 import java.net.MalformedURLException;
@@ -141,6 +143,16 @@
     }
 
     @Override
+    public void setNamedLong(String name, long value) {
+        VarName entry = mVarNameHashMap.get(name);
+        if (entry != null) {
+            int id = entry.mId;
+            LongConstant longConstant = (LongConstant) mRemoteComposeState.getObject(id);
+            longConstant.setValue(value);
+        }
+    }
+
+    @Override
     public void setNamedDataOverride(String dataName, Object value) {
         if (mVarNameHashMap.get(dataName) != null) {
             int id = mVarNameHashMap.get(dataName).mId;
@@ -215,6 +227,27 @@
                         case BitmapData.TYPE_PNG_8888:
                             image = BitmapFactory.decodeByteArray(data, 0, data.length);
                             break;
+                        case BitmapData.TYPE_PNG_ALPHA_8:
+                            image = decodePreferringAlpha8(data);
+
+                            // If needed convert to ALPHA_8.
+                            if (!image.getConfig().equals(Bitmap.Config.ALPHA_8)) {
+                                Bitmap alpha8Bitmap =
+                                        Bitmap.createBitmap(
+                                                image.getWidth(),
+                                                image.getHeight(),
+                                                Bitmap.Config.ALPHA_8);
+                                Canvas canvas = new Canvas(alpha8Bitmap);
+                                Paint paint = new Paint();
+                                paint.setXfermode(
+                                        new android.graphics.PorterDuffXfermode(
+                                                android.graphics.PorterDuff.Mode.SRC));
+                                canvas.drawBitmap(image, 0, 0, paint);
+                                image.recycle(); // Release resources
+
+                                image = alpha8Bitmap;
+                            }
+                            break;
                         case BitmapData.TYPE_RAW8888:
                             image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                             int[] idata = new int[data.length / 4];
@@ -255,6 +288,12 @@
         }
     }
 
+    private Bitmap decodePreferringAlpha8(@NonNull byte[] data) {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.ALPHA_8;
+        return BitmapFactory.decodeByteArray(data, 0, data.length, options);
+    }
+
     @Override
     public void loadText(int id, @NonNull String text) {
         if (!mRemoteComposeState.containsId(id)) {
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
index 4d2dd05..29cd40d 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -200,6 +200,16 @@
         mARContext.setNamedColorOverride(colorName, colorValue);
     }
 
+    /**
+     * set the value of a long associated with this name.
+     *
+     * @param name Name of color typically "android.xxx"
+     * @param value the long value
+     */
+    public void setLong(String name, long value) {
+        mARContext.setNamedLong(name, value);
+    }
+
     public RemoteComposeDocument getDocument() {
         return mDocument;
     }
diff --git a/core/proto/android/net/OWNERS b/core/proto/android/net/OWNERS
index 509699b..a6627fe 100644
--- a/core/proto/android/net/OWNERS
+++ b/core/proto/android/net/OWNERS
@@ -1,3 +1,2 @@
-ek@google.com
 lorenzo@google.com
 satk@google.com
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 34ec148..8de7746 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -111,6 +111,7 @@
         optional SettingProto autoclick_cursor_area_size = 62 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto autoclick_ignore_minor_cursor_movement = 63 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto autoclick_panel_position = 64 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto autoclick_revert_to_left_click = 65 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     }
     optional Accessibility accessibility = 2;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ee6899c..e16ce98 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8995,13 +8995,13 @@
 
     <!-- @SystemApi
         @FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled")
-        This permission is required to access the specific text classifier you need from the
+        This permission is required to access the specific text classifier from the
         TextClassificationManager.
-        <p>Protection level: signature|role
+        <p>Protection level: signature|role|privileged
     @hide
     -->
     <permission android:name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"
-        android:protectionLevel="signature|role"
+        android:protectionLevel="signature|role|privileged"
         android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/>
 
     <!-- Attribution for Geofencing service. -->
diff --git a/core/res/res/drawable/ic_accessibility_autoclick.xml b/core/res/res/drawable/ic_accessibility_autoclick.xml
new file mode 100644
index 0000000..44d34d3
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_autoclick.xml
@@ -0,0 +1,8 @@
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/accessibility_autoclick_background" />
+    <foreground>
+        <inset
+            android:drawable="@drawable/ic_accessibility_autoclick_foreground"
+            android:inset="@dimen/accessibility_icon_foreground_padding_ratio" />
+    </foreground>
+</adaptive-icon>
diff --git a/core/res/res/drawable/ic_accessibility_autoclick_foreground.xml b/core/res/res/drawable/ic_accessibility_autoclick_foreground.xml
new file mode 100644
index 0000000..0a76a1b
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_autoclick_foreground.xml
@@ -0,0 +1,17 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40dp"
+    android:height="41dp"
+    android:viewportWidth="40"
+    android:viewportHeight="41">
+
+    <path
+        android:fillColor="#67D4FF"
+        android:pathData="M0 20.3789C0 9.33321 8.95431 0.378906 20 0.378906C31.0457 0.378906 40 9.33321 40 20.3789C40 31.4246 31.0457 40.3789 20 40.3789C8.95431 40.3789 0 31.4246 0 20.3789Z" />
+    <path
+        android:fillColor="#04409F"
+        android:pathData="M28.9564 32.0587L24.0973 27.1996L22.6765 31.4904L19.2666 20.124L30.633 23.5339L26.3422 24.9547L31.2013 29.8138L28.9564 32.0587Z" />
+    <path
+        android:fillColor="#04409F"
+        android:fillType="evenOdd"
+        android:pathData="M8.01596 20.8918C8.10753 22.8222 8.63239 24.6433 9.60868 26.3343C10.9014 28.5733 12.8274 30.221 15.1666 31.2713C16.9169 32.0453 18.7173 32.3866 20.5679 32.2953L19.9158 30.1502C18.5615 30.1389 17.2456 29.8364 15.9682 29.2427C14.0941 28.3832 12.6373 27.0532 11.5977 25.2526C10.7841 23.8434 10.3491 22.3305 10.2925 20.7139C10.2607 19.104 10.2783 17.806 10.9916 16.3295C10.9916 16.3295 11.4971 15.2881 11.6974 14.9932C11.8978 14.6983 12.7644 13.5882 12.7644 13.5882L11.3665 11.8494C11.3665 11.8494 10.6288 12.6605 10.291 13.1593C10.1116 13.4242 9.64185 14.1332 9.64185 14.1332L9.07295 15.2304C8.24895 17.0214 7.92073 18.8842 8.01596 20.8918ZM31.9633 21.2755L29.8026 20.6995C29.8208 20.1549 29.797 19.6129 29.7312 19.0733C29.6285 18.1723 29.3858 17.2999 29.0031 16.4562L30.8819 15.3714C31.3937 16.4747 31.7295 17.6169 31.8895 18.798C32.0056 19.6202 32.0302 20.4461 31.9633 21.2755ZM18.4931 8.55776C17.312 8.71775 16.1653 9.04578 15.053 9.54184L16.1513 11.4442C16.995 11.0615 17.8674 10.8188 18.7684 10.7161C19.6851 10.6043 20.6091 10.6137 21.5403 10.7441L22.1061 8.63249C20.8942 8.41364 19.6899 8.38872 18.4931 8.55776ZM24.1807 9.18837L23.6149 11.3C24.4865 11.6526 25.2869 12.0987 26.0158 12.6382C26.7448 13.1776 27.379 13.824 27.9183 14.5773L29.7972 13.4925C29.1223 12.5043 28.3055 11.6502 27.347 10.9302C26.4042 10.2011 25.3487 9.62046 24.1807 9.18837Z" />
+</vector>
diff --git a/core/res/res/drawable/ic_notification_summarization.xml b/core/res/res/drawable/ic_notification_summarization.xml
index d476872..acfd90e 100644
--- a/core/res/res/drawable/ic_notification_summarization.xml
+++ b/core/res/res/drawable/ic_notification_summarization.xml
@@ -16,9 +16,16 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="16dp"
     android:height="16dp"
-    android:tint="?android:attr/colorControlNormal"
-    android:viewportHeight="960"
-    android:viewportWidth="960">
-    <path android:fillColor="#ffffff"
-          android:pathData="M120,840L120,760L600,760L600,840L120,840ZM120,640L120,560L840,560L840,640L120,640ZM120,440L120,360L560,360L560,440L120,440ZM700,480Q700,388 636,324Q572,260 480,260Q572,260 636,196Q700,132 700,40Q700,132 764,196Q828,260 920,260Q828,260 764,324Q700,388 700,480Z"/>
+    android:tint="@color/materialColorPrimary"
+    android:viewportHeight="14"
+    android:viewportWidth="14">
+    <path
+        android:pathData="M10,9C9.986,9 9.979,8.993 9.979,8.979C9.979,8.431 9.875,7.917 9.667,7.438C9.465,6.958 9.184,6.538 8.823,6.177C8.462,5.816 8.042,5.535 7.563,5.333C7.083,5.125 6.569,5.021 6.021,5.021C6.007,5.021 6,5.014 6,5C6,4.986 6.007,4.979 6.021,4.979C6.569,4.979 7.083,4.878 7.563,4.677C8.042,4.469 8.462,4.184 8.823,3.823C9.184,3.462 9.465,3.042 9.667,2.563C9.875,2.083 9.979,1.569 9.979,1.021C9.979,1.007 9.986,1 10,1C10.014,1 10.021,1.007 10.021,1.021C10.021,1.569 10.122,2.083 10.323,2.563C10.531,3.042 10.816,3.462 11.177,3.823C11.538,4.184 11.958,4.469 12.438,4.677C12.917,4.878 13.431,4.979 13.979,4.979C13.993,4.979 14,4.986 14,5C14,5.014 13.993,5.021 13.979,5.021C13.431,5.021 12.917,5.125 12.438,5.333C11.958,5.535 11.538,5.816 11.177,6.177C10.816,6.538 10.531,6.958 10.323,7.438C10.122,7.917 10.021,8.431 10.021,8.979C10.021,8.993 10.014,9 10,9Z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M1,10.456C1,10.204 1.204,10 1.456,10H12.544C12.796,10 13,10.204 13,10.456V11.544C13,11.796 12.796,12 12.544,12H1.456C1.204,12 1,11.796 1,11.544V10.456Z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M1,7.456C1,7.204 1.204,7 1.456,7H6.544C6.796,7 7,7.204 7,7.456V8.544C7,8.796 6.796,9 6.544,9H1.456C1.204,9 1,8.796 1,8.544V7.456Z"
+        android:fillColor="#ffffff"/>
 </vector>
\ No newline at end of file
diff --git a/core/res/res/layout/accessibility_autoclick_type_panel.xml b/core/res/res/layout/accessibility_autoclick_type_panel.xml
index cedbdc1..902ef7f 100644
--- a/core/res/res/layout/accessibility_autoclick_type_panel.xml
+++ b/core/res/res/layout/accessibility_autoclick_type_panel.xml
@@ -17,7 +17,7 @@
 */
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.server.accessibility.autoclick.AutoclickLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/accessibility_autoclick_type_panel"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
@@ -130,4 +130,4 @@
 
     </LinearLayout>
 
-</LinearLayout>
+</com.android.server.accessibility.autoclick.AutoclickLinearLayout>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 9983c45..0905ae0 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -854,8 +854,21 @@
          <aside class="note"><b>Note:</b>
             <ul>
                 <li>To improve the layout of apps on form factors with smallest width >= 600dp, the
-                    system ignores this attribute for apps that target Android 16 (API level 36) or
-                    higher.</li>
+                    system ignores the following values of this attribute for apps that target
+                    Android 16 (API level 36) or higher:
+                    <ul>
+                      <li><code>portrait</code></li>
+                      <li><code>landscape</code></li>
+                      <li><code>reversePortrait</code></li>
+                      <li><code>reverseLandscape</code></li>
+                      <li><code>sensorPortrait</code></li>
+                      <li><code>sensorLandscape</code></li>
+                      <li><code>userPortrait</code></li>
+                      <li><code>userLandscape</code></li>
+                    </ul>
+                    <p>The values are treated as if the app had set orientation as
+                       <code>unspecified</code>.</p>
+                </li>
                 <li>Device manufacturers can configure devices to override (ignore) this attribute
                     to improve the layout of apps.</li>
                 <li>On devices with Android 16 (API level 36) or higher installed, virtual device
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 4e93c44..bf1423b 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -557,6 +557,7 @@
     <color name="accessibility_magnification_background">#F50D60</color>
     <color name="accessibility_daltonizer_background">#00BCD4</color>
     <color name="accessibility_color_inversion_background">#546E7A</color>
+    <color name="accessibility_autoclick_background">#67D4FF</color>
 
     <!-- Fullscreen magnification thumbnail color -->
     <color name="accessibility_magnification_thumbnail_stroke_color">#0C0C0C</color>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c8c1e73..a18c1d4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5652,6 +5652,7 @@
   <java-symbol type="id" name="accessibility_autoclick_position_button" />
   <java-symbol type="drawable" name="accessibility_autoclick_pause" />
   <java-symbol type="drawable" name="accessibility_autoclick_resume" />
+  <java-symbol type="drawable" name="ic_accessibility_autoclick" />
 
   <!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization -->
   <java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
index 6915015..790ac4a 100644
--- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java
+++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
@@ -33,6 +33,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -611,6 +612,7 @@
 
     @Test
     @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+    @Ignore("b/303199244")
     public void testMessageQueue() throws Exception {
         TraceConfig traceConfig = getTraceConfig("mq");
 
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 15f7029..9234902 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -619,6 +619,8 @@
         <permission name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"/>
         <permission name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"/>
         <permission name="android.permission.READ_COLOR_ZONES"/>
+        <!-- Permission required for CTS test - CtsTextClassifierTestCases -->
+        <permission name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml
index 23c9caf..f6a176f 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -39,6 +39,8 @@
 
     <!-- Bubble drop target dimensions -->
     <dimen name="drop_target_elevation">1dp</dimen>
+    <dimen name="drop_target_radius">28dp</dimen>
+    <dimen name="drop_target_stroke">1dp</dimen>
     <dimen name="drop_target_full_screen_padding">20dp</dimen>
     <dimen name="drop_target_desktop_window_padding_small">100dp</dimen>
     <dimen name="drop_target_desktop_window_padding_large">130dp</dimen>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
index 2dc183f..5c25908 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
@@ -18,29 +18,34 @@
 
 import android.content.Context
 import android.graphics.Rect
-import android.view.View
+import android.graphics.RectF
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.widget.FrameLayout
 import androidx.core.animation.Animator
 import androidx.core.animation.AnimatorListenerAdapter
 import androidx.core.animation.ValueAnimator
+import com.android.wm.shell.shared.R
 
 /**
  * Manages animating drop targets in response to dragging bubble icons or bubble expanded views
  * across different drag zones.
  */
 class DropTargetManager(
-    context: Context,
+    private val context: Context,
     private val container: FrameLayout,
     private val isLayoutRtl: Boolean,
     private val dragZoneChangedListener: DragZoneChangedListener,
 ) {
 
     private var state: DragState? = null
-    private val dropTargetView = View(context)
+    private val dropTargetView = DropTargetView(context)
     private var animator: ValueAnimator? = null
+    private var morphRect: RectF = RectF(0f, 0f, 0f, 0f)
 
     private companion object {
-        const val ANIMATION_DURATION_MS = 250L
+        const val MORPH_ANIM_DURATION = 250L
+        const val DROP_TARGET_ALPHA_IN_DURATION = 150L
+        const val DROP_TARGET_ALPHA_OUT_DURATION = 100L
     }
 
     /** Must be called when a drag gesture is starting. */
@@ -55,15 +60,10 @@
     private fun setupDropTarget() {
         if (dropTargetView.parent != null) container.removeView(dropTargetView)
         container.addView(dropTargetView, 0)
-        // TODO b/393173014: set elevation and background
         dropTargetView.alpha = 0f
-        dropTargetView.scaleX = 1f
-        dropTargetView.scaleY = 1f
-        dropTargetView.translationX = 0f
-        dropTargetView.translationY = 0f
-        // the drop target is added with a width and height of 1 pixel. when it gets resized, we use
-        // set its scale to the width and height of the bounds it should have to avoid layout passes
-        dropTargetView.layoutParams = FrameLayout.LayoutParams(/* width= */ 1, /* height= */ 1)
+        dropTargetView.elevation = context.resources.getDimension(R.dimen.drop_target_elevation)
+        // Match parent and the target is drawn within the view
+        dropTargetView.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
     }
 
     /** Called when the user drags to a new location. */
@@ -92,10 +92,7 @@
         when {
             dropTargetBounds == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f)
             dropTargetView.alpha == 0f -> {
-                dropTargetView.translationX = dropTargetBounds.exactCenterX()
-                dropTargetView.translationY = dropTargetBounds.exactCenterY()
-                dropTargetView.scaleX = dropTargetBounds.width().toFloat()
-                dropTargetView.scaleY = dropTargetBounds.height().toFloat()
+                dropTargetView.update(RectF(dropTargetBounds))
                 startFadeAnimation(from = 0f, to = 1f)
             }
             else -> startMorphAnimation(dropTargetBounds)
@@ -104,7 +101,9 @@
 
     private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) {
         animator?.cancel()
-        val animator = ValueAnimator.ofFloat(from, to).setDuration(ANIMATION_DURATION_MS)
+        val duration =
+            if (from < to) DROP_TARGET_ALPHA_IN_DURATION else DROP_TARGET_ALPHA_OUT_DURATION
+        val animator = ValueAnimator.ofFloat(from, to).setDuration(duration)
         animator.addUpdateListener { _ -> dropTargetView.alpha = animator.animatedValue as Float }
         if (onEnd != null) {
             animator.doOnEnd(onEnd)
@@ -113,23 +112,20 @@
         animator.start()
     }
 
-    private fun startMorphAnimation(bounds: Rect) {
+    private fun startMorphAnimation(endBounds: Rect) {
         animator?.cancel()
         val startAlpha = dropTargetView.alpha
-        val startTx = dropTargetView.translationX
-        val startTy = dropTargetView.translationY
-        val startScaleX = dropTargetView.scaleX
-        val startScaleY = dropTargetView.scaleY
-        val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS)
+        val startRect = dropTargetView.getRect()
+        val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(MORPH_ANIM_DURATION)
         animator.addUpdateListener { _ ->
             val fraction = animator.animatedValue as Float
             dropTargetView.alpha = startAlpha + (1 - startAlpha) * fraction
-            dropTargetView.translationX = startTx + (bounds.exactCenterX() - startTx) * fraction
-            dropTargetView.translationY = startTy + (bounds.exactCenterY() - startTy) * fraction
-            dropTargetView.scaleX =
-                startScaleX + (bounds.width().toFloat() - startScaleX) * fraction
-            dropTargetView.scaleY =
-                startScaleY + (bounds.height().toFloat() - startScaleY) * fraction
+
+            morphRect.left = (startRect.left + (endBounds.left - startRect.left) * fraction)
+            morphRect.top = (startRect.top + (endBounds.top - startRect.top) * fraction)
+            morphRect.right = (startRect.right + (endBounds.right - startRect.right) * fraction)
+            morphRect.bottom = (startRect.bottom + (endBounds.bottom - startRect.bottom) * fraction)
+            dropTargetView.update(morphRect)
         }
         this.animator = animator
         animator.start()
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt
new file mode 100644
index 0000000..2bb6cf4
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.shared.bubbles
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.RectF
+import android.view.View
+import com.android.wm.shell.shared.R
+
+/**
+ * Shows a drop target within this view.
+ */
+class DropTargetView(context: Context) : View(context) {
+
+    private val rectPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+        color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
+        style = Paint.Style.FILL
+        alpha = (0.35f * 255).toInt()
+    }
+
+    private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+        color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
+        style = Paint.Style.STROKE
+        strokeWidth = context.resources.getDimensionPixelSize(R.dimen.drop_target_stroke).toFloat()
+    }
+
+    private val cornerRadius = context.resources.getDimensionPixelSize(
+        R.dimen.drop_target_radius).toFloat()
+
+    private val rect = RectF(0f, 0f, 0f, 0f)
+
+    override fun onDraw(canvas: Canvas) {
+        canvas.save()
+        canvas.drawRoundRect(rect, cornerRadius, cornerRadius, rectPaint)
+        canvas.drawRoundRect(rect, cornerRadius, cornerRadius, strokePaint)
+        canvas.restore()
+    }
+
+    fun update(positionRect: RectF) {
+        rect.set(positionRect)
+        invalidate()
+    }
+
+    fun getRect(): RectF {
+        return RectF(rect)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
index b507ca2..3f21e74 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import android.app.ActivityManager;
+import android.window.DesktopExperienceFlags;
 import android.window.DisplayAreaInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
@@ -67,7 +68,7 @@
 
     /** Returns whether PiP in Connected Displays is enabled by checking the flag. */
     public boolean isConnectedDisplaysPipEnabled() {
-        return Flags.enableConnectedDisplaysPip();
+        return DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue();
     }
 
     /** Returns whether the display with the PiP task is in freeform windowing mode. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt
index 7074e8b..6c6d830 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt
@@ -21,6 +21,7 @@
 import android.os.Handler
 import android.os.IBinder
 import android.view.SurfaceControl.Transaction
+import android.view.WindowManager.TRANSIT_TO_BACK
 import android.window.TransitionInfo
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
@@ -61,7 +62,9 @@
         finishTransaction: Transaction,
         finishCallback: Transitions.TransitionFinishCallback,
     ): Boolean {
-        if (!TransitionUtil.isClosingType(info.type)) return false
+        val shouldAnimate =
+            TransitionUtil.isClosingType(info.type) || info.type == Transitions.TRANSIT_MINIMIZE
+        if (!shouldAnimate) return false
 
         val animations = mutableListOf<Animator>()
         val onAnimFinish: (Animator) -> Unit = { animator ->
@@ -75,10 +78,14 @@
             }
         }
 
+        val checkChangeMode = { change: TransitionInfo.Change ->
+            change.mode == info.type ||
+                (info.type == Transitions.TRANSIT_MINIMIZE && change.mode == TRANSIT_TO_BACK)
+        }
         animations +=
             info.changes
                 .filter {
-                    it.mode == info.type && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+                    checkChangeMode(it) && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
                 }
                 .mapNotNull { createMinimizeAnimation(it, finishTransaction, onAnimFinish) }
         if (animations.isEmpty()) return false
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 5de3be4..8f7e52e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -677,11 +677,7 @@
                 // Bring other apps to front first.
                 bringDesktopAppsToFrontBeforeShowingNewTask(displayId, wct, task.taskId)
             }
-        if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
-            prepareMoveTaskToDesk(wct, task, deskId)
-        } else {
-            addMoveToDesktopChanges(wct, task)
-        }
+        addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId)
         return taskIdToMinimize
     }
 
@@ -1260,6 +1256,8 @@
      * Move [task] to display with [displayId].
      *
      * No-op if task is already on that display per [RunningTaskInfo.displayId].
+     *
+     * TODO: b/399411604 - split this up into smaller functions.
      */
     private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
         logV("moveToDisplay: taskId=%d displayId=%d", task.taskId, displayId)
@@ -1315,16 +1313,20 @@
 
         // TODO: b/393977830 and b/397437641 - do not assume that freeform==desktop.
         if (!task.isFreeform) {
-            addMoveToDesktopChanges(wct, task, displayId)
-        } else if (Flags.enableMoveToNextDisplayShortcut()) {
-            applyFreeformDisplayChange(wct, task, displayId)
+            addMoveToDeskTaskChanges(wct = wct, task = task, deskId = destinationDeskId)
+        } else {
+            if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+                desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task)
+            }
+            if (Flags.enableMoveToNextDisplayShortcut()) {
+                applyFreeformDisplayChange(wct, task, displayId)
+            }
         }
 
-        if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
-            desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task)
-        } else {
+        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
             wct.reparent(task.token, displayAreaInfo.token, /* onTop= */ true)
         }
+
         addDeskActivationChanges(destinationDeskId, wct)
         val activationRunnable: RunOnTransitStart = { transition ->
             desksTransitionObserver.addPendingTransition(
@@ -2062,12 +2064,13 @@
             triggerTask?.let { task ->
                 when {
                     // Check if freeform task launch during recents should be handled
-                    shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task)
+                    shouldHandleMidRecentsFreeformLaunch ->
+                        handleMidRecentsFreeformTaskLaunch(task, transition)
                     // Check if the closing task needs to be handled
                     TransitionUtil.isClosingType(request.type) ->
                         handleTaskClosing(task, transition, request.type)
                     // Check if the top task shouldn't be allowed to enter desktop mode
-                    isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task)
+                    isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task, transition)
                     // Check if fullscreen task should be updated
                     task.isFullscreen -> handleFullscreenTaskLaunch(task, transition)
                     // Check if freeform task should be updated
@@ -2306,20 +2309,23 @@
      * This is a special case where we want to launch the task in fullscreen instead of freeform.
      */
     private fun handleMidRecentsFreeformTaskLaunch(
-        task: RunningTaskInfo
+        task: RunningTaskInfo,
+        transition: IBinder,
     ): WindowContainerTransaction? {
         logV("DesktopTasksController: handleMidRecentsFreeformTaskLaunch")
         val wct = WindowContainerTransaction()
-        addMoveToFullscreenChanges(
-            wct = wct,
-            taskInfo = task,
-            willExitDesktop =
-                willExitDesktop(
-                    triggerTaskId = task.taskId,
-                    displayId = task.displayId,
-                    forceExitDesktop = true,
-                ),
-        )
+        val runOnTransitStart =
+            addMoveToFullscreenChanges(
+                wct = wct,
+                taskInfo = task,
+                willExitDesktop =
+                    willExitDesktop(
+                        triggerTaskId = task.taskId,
+                        displayId = task.displayId,
+                        forceExitDesktop = true,
+                    ),
+            )
+        runOnTransitStart?.invoke(transition)
         wct.reorder(task.token, true)
         return wct
     }
@@ -2343,16 +2349,18 @@
                 // launched. We should make this task go to fullscreen instead of freeform. Note
                 // that this means any re-launch of a freeform window outside of desktop will be in
                 // fullscreen as long as default-desktop flag is disabled.
-                addMoveToFullscreenChanges(
-                    wct = wct,
-                    taskInfo = task,
-                    willExitDesktop =
-                        willExitDesktop(
-                            triggerTaskId = task.taskId,
-                            displayId = task.displayId,
-                            forceExitDesktop = true,
-                        ),
-                )
+                val runOnTransitStart =
+                    addMoveToFullscreenChanges(
+                        wct = wct,
+                        taskInfo = task,
+                        willExitDesktop =
+                            willExitDesktop(
+                                triggerTaskId = task.taskId,
+                                displayId = task.displayId,
+                                forceExitDesktop = true,
+                            ),
+                    )
+                runOnTransitStart?.invoke(transition)
                 return wct
             }
             bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
@@ -2416,7 +2424,8 @@
         if (shouldFullscreenTaskLaunchSwitchToDesktop(task)) {
             logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId)
             return WindowContainerTransaction().also { wct ->
-                addMoveToDesktopChanges(wct, task)
+                val deskId = getDefaultDeskId(task.displayId)
+                addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId)
                 // In some launches home task is moved behind new task being launched. Make sure
                 // that's not the case for launches in desktop. Also, if this launch is the first
                 // one to trigger the desktop mode (e.g., when [forceEnterDesktop()]), activate the
@@ -2447,7 +2456,8 @@
             // If a freeform task receives a request for a fullscreen launch, apply the same
             // changes we do for similar transitions. The task not having WINDOWING_MODE_UNDEFINED
             // set when needed can interfere with future split / multi-instance transitions.
-            return WindowContainerTransaction().also { wct ->
+            val wct = WindowContainerTransaction()
+            val runOnTransitStart =
                 addMoveToFullscreenChanges(
                     wct = wct,
                     taskInfo = task,
@@ -2458,7 +2468,8 @@
                             forceExitDesktop = true,
                         ),
                 )
-            }
+            runOnTransitStart?.invoke(transition)
+            return wct
         }
         return null
     }
@@ -2473,7 +2484,10 @@
      * If a task is not compatible with desktop mode freeform, it should always be launched in
      * fullscreen.
      */
-    private fun handleIncompatibleTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+    private fun handleIncompatibleTaskLaunch(
+        task: RunningTaskInfo,
+        transition: IBinder,
+    ): WindowContainerTransaction? {
         logV("handleIncompatibleTaskLaunch")
         if (!isDesktopModeShowing(task.displayId) && !forceEnterDesktop(task.displayId)) return null
         // Only update task repository for transparent task.
@@ -2485,7 +2499,8 @@
         }
         // Already fullscreen, no-op.
         if (task.isFullscreen) return null
-        return WindowContainerTransaction().also { wct ->
+        val wct = WindowContainerTransaction()
+        val runOnTransitStart =
             addMoveToFullscreenChanges(
                 wct = wct,
                 taskInfo = task,
@@ -2496,7 +2511,8 @@
                         forceExitDesktop = true,
                     ),
             )
-        }
+        runOnTransitStart?.invoke(transition)
+        return wct
     }
 
     /**
@@ -2543,55 +2559,44 @@
     }
 
     /**
-     * Apply all changes required when task is first added to desktop. Uses the task's current
-     * display by default to apply initial bounds and placement relative to the display. Use a
-     * different [displayId] if the task should be moved to a different display.
+     * Applies the [wct] changes needed when a task is first moving to a desk.
+     *
+     * Note that this recalculates the initial bounds of the task, so it should not be used when
+     * transferring a task between desks.
+     *
+     * TODO: b/362720497 - this should be improved to be reusable by desk-to-desk CUJs where
+     *   [DesksOrganizer.moveTaskToDesk] needs to be called and even cross-display CUJs where
+     *   [applyFreeformDisplayChange] needs to be called. Potentially by comparing source vs
+     *   destination desk ids and display ids, or adding extra arguments to the function.
      */
-    @VisibleForTesting
-    @Deprecated("Deprecated with multiple desks", ReplaceWith("prepareMoveTaskToDesk()"))
-    fun addMoveToDesktopChanges(
+    fun addMoveToDeskTaskChanges(
         wct: WindowContainerTransaction,
-        taskInfo: RunningTaskInfo,
-        displayId: Int = taskInfo.displayId,
-    ) {
-        val displayLayout = displayController.getDisplayLayout(displayId) ?: return
-        val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)!!
-        val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
-        // TODO: b/397437641 - reconsider the windowing mode choice when multiple desks is enabled.
-        val targetWindowingMode =
-            if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
-                // Display windowing is freeform, set to undefined and inherit it
-                WINDOWING_MODE_UNDEFINED
-            } else {
-                WINDOWING_MODE_FREEFORM
-            }
-        val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId)
-
-        if (canChangeTaskPosition(taskInfo)) {
-            wct.setBounds(taskInfo.token, initialBounds)
-        }
-        wct.setWindowingMode(taskInfo.token, targetWindowingMode)
-        wct.reorder(taskInfo.token, /* onTop= */ true)
-        if (useDesktopOverrideDensity()) {
-            wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE)
-        }
-    }
-
-    private fun prepareMoveTaskToDesk(
-        wct: WindowContainerTransaction,
-        taskInfo: RunningTaskInfo,
+        task: RunningTaskInfo,
         deskId: Int,
     ) {
-        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
-        val displayId = taskRepository.getDisplayForDesk(deskId)
-        val displayLayout = displayController.getDisplayLayout(displayId) ?: return
-        val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId)
-        if (canChangeTaskPosition(taskInfo)) {
-            wct.setBounds(taskInfo.token, initialBounds)
+        val targetDisplayId = taskRepository.getDisplayForDesk(deskId)
+        val displayLayout = displayController.getDisplayLayout(targetDisplayId) ?: return
+        val initialBounds = getInitialBounds(displayLayout, task, targetDisplayId)
+        if (canChangeTaskPosition(task)) {
+            wct.setBounds(task.token, initialBounds)
         }
-        desksOrganizer.moveTaskToDesk(wct, deskId = deskId, task = taskInfo)
+        if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+            desksOrganizer.moveTaskToDesk(wct = wct, deskId = deskId, task = task)
+        } else {
+            val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(targetDisplayId)!!
+            val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+            val targetWindowingMode =
+                if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
+                    // Display windowing is freeform, set to undefined and inherit it
+                    WINDOWING_MODE_UNDEFINED
+                } else {
+                    WINDOWING_MODE_FREEFORM
+                }
+            wct.setWindowingMode(task.token, targetWindowingMode)
+            wct.reorder(task.token, /* onTop= */ true)
+        }
         if (useDesktopOverrideDensity()) {
-            wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE)
+            wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE)
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index d666126..c0a0f46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss;
 import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
@@ -200,7 +201,8 @@
                     transition, info, startTransaction, finishTransaction, finishCallback);
         }
 
-        if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
+        if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0
+                || (info.getFlags() & TRANSIT_FLAG_AOD_APPEARING) != 0) {
             return startAnimation(mAppearTransition, "appearing",
                     transition, info, startTransaction, finishTransaction, finishCallback);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
index a033b824..7918a21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
@@ -131,9 +131,10 @@
     }
 
     private void onAlphaAnimationUpdate(float alpha, SurfaceControl.Transaction tx) {
+        // only set shadow radius on fade in
         tx.setAlpha(mLeash, alpha)
                 .setCornerRadius(mLeash, mCornerRadius)
-                .setShadowRadius(mLeash, mShadowRadius);
+                .setShadowRadius(mLeash, mDirection == FADE_IN ? mShadowRadius : 0f);
         tx.apply();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index d16c578..6012fe6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -32,6 +32,7 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.SurfaceControl;
+import android.window.DesktopExperienceFlags;
 import android.window.DisplayAreaInfo;
 import android.window.WindowContainerTransaction;
 
@@ -41,7 +42,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLog;
 import com.android.internal.util.Preconditions;
-import com.android.window.flags.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayChangeController;
@@ -303,7 +303,8 @@
     public void onDisplayRemoved(int displayId) {
         // If PiP was active on an external display that is removed, clean up states and set
         // {@link PipDisplayLayoutState} to DEFAULT_DISPLAY.
-        if (Flags.enableConnectedDisplaysPip() && mPipTransitionState.isInPip()
+        if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue()
+                && mPipTransitionState.isInPip()
                 && displayId == mPipDisplayLayoutState.getDisplayId()
                 && displayId != DEFAULT_DISPLAY) {
             mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
@@ -385,7 +386,7 @@
 
         // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
         // display info that PiP is entering in.
-        if (Flags.enableConnectedDisplaysPip()) {
+        if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue()) {
             final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
             if (displayLayout != null) {
                 mPipDisplayLayoutState.setDisplayId(displayId);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt
index 0d1c5722..3e6f688 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt
@@ -33,6 +33,7 @@
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.transition.Transitions
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertTrue
@@ -154,6 +155,24 @@
         assertTrue("Should animate going to back freeform task close transition", animates)
     }
 
+    @Test
+    fun startAnimation_minimizeTransitionToBackFreeformTask_returnsTrue() {
+        val animates =
+            handler.startAnimation(
+                transition = mock(),
+                info =
+                    createTransitionInfo(
+                        type = Transitions.TRANSIT_MINIMIZE,
+                        task = createTask(WINDOWING_MODE_FREEFORM),
+                    ),
+                startTransaction = mock(),
+                finishTransaction = mock(),
+                finishCallback = {},
+            )
+
+        assertTrue("Should animate going to back freeform task minimize transition", animates)
+    }
+
     private fun createTransitionInfo(
         type: Int = WindowManager.TRANSIT_TO_BACK,
         changeMode: Int = WindowManager.TRANSIT_TO_BACK,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index b0785df..63bf6841 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1114,44 +1114,44 @@
     }
 
     @Test
-    fun addMoveToDesktopChanges_gravityLeft_noBoundsApplied() {
+    fun addMoveToDeskTaskChanges_gravityLeft_noBoundsApplied() {
         setUpLandscapeDisplay()
         val task = setUpFullscreenTask(gravity = Gravity.LEFT)
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(finalBounds).isEqualTo(Rect())
     }
 
     @Test
-    fun addMoveToDesktopChanges_gravityRight_noBoundsApplied() {
+    fun addMoveToDeskTaskChanges_gravityRight_noBoundsApplied() {
         setUpLandscapeDisplay()
         val task = setUpFullscreenTask(gravity = Gravity.RIGHT)
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(finalBounds).isEqualTo(Rect())
     }
 
     @Test
-    fun addMoveToDesktopChanges_gravityTop_noBoundsApplied() {
+    fun addMoveToDeskTaskChanges_gravityTop_noBoundsApplied() {
         setUpLandscapeDisplay()
         val task = setUpFullscreenTask(gravity = Gravity.TOP)
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(finalBounds).isEqualTo(Rect())
     }
 
     @Test
-    fun addMoveToDesktopChanges_gravityBottom_noBoundsApplied() {
+    fun addMoveToDeskTaskChanges_gravityBottom_noBoundsApplied() {
         setUpLandscapeDisplay()
         val task = setUpFullscreenTask(gravity = Gravity.BOTTOM)
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(finalBounds).isEqualTo(Rect())
@@ -1192,7 +1192,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
-    fun addMoveToDesktopChanges_positionBottomRight() {
+    fun addMoveToDeskTaskChanges_positionBottomRight() {
         setUpLandscapeDisplay()
         val stableBounds = Rect()
         displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1201,7 +1201,7 @@
 
         val task = setUpFullscreenTask()
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1210,7 +1210,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
-    fun addMoveToDesktopChanges_positionTopLeft() {
+    fun addMoveToDeskTaskChanges_positionTopLeft() {
         setUpLandscapeDisplay()
         val stableBounds = Rect()
         displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1219,7 +1219,7 @@
 
         val task = setUpFullscreenTask()
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1228,7 +1228,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
-    fun addMoveToDesktopChanges_positionBottomLeft() {
+    fun addMoveToDeskTaskChanges_positionBottomLeft() {
         setUpLandscapeDisplay()
         val stableBounds = Rect()
         displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1237,7 +1237,7 @@
 
         val task = setUpFullscreenTask()
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1246,7 +1246,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
-    fun addMoveToDesktopChanges_positionTopRight() {
+    fun addMoveToDeskTaskChanges_positionTopRight() {
         setUpLandscapeDisplay()
         val stableBounds = Rect()
         displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1255,7 +1255,7 @@
 
         val task = setUpFullscreenTask()
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1264,7 +1264,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
-    fun addMoveToDesktopChanges_positionResetsToCenter() {
+    fun addMoveToDeskTaskChanges_positionResetsToCenter() {
         setUpLandscapeDisplay()
         val stableBounds = Rect()
         displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1273,7 +1273,7 @@
 
         val task = setUpFullscreenTask()
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1282,7 +1282,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
-    fun addMoveToDesktopChanges_lastWindowSnapLeft_positionResetsToCenter() {
+    fun addMoveToDeskTaskChanges_lastWindowSnapLeft_positionResetsToCenter() {
         setUpLandscapeDisplay()
         val stableBounds = Rect()
         displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1294,7 +1294,7 @@
 
         val task = setUpFullscreenTask()
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1303,7 +1303,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
-    fun addMoveToDesktopChanges_lastWindowSnapRight_positionResetsToCenter() {
+    fun addMoveToDeskTaskChanges_lastWindowSnapRight_positionResetsToCenter() {
         setUpLandscapeDisplay()
         val stableBounds = Rect()
         displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1321,7 +1321,7 @@
 
         val task = setUpFullscreenTask()
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1330,7 +1330,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
-    fun addMoveToDesktopChanges_lastWindowMaximised_positionResetsToCenter() {
+    fun addMoveToDeskTaskChanges_lastWindowMaximised_positionResetsToCenter() {
         setUpLandscapeDisplay()
         val stableBounds = Rect()
         displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1340,7 +1340,7 @@
 
         val task = setUpFullscreenTask()
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1349,7 +1349,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
-    fun addMoveToDesktopChanges_defaultToCenterIfFree() {
+    fun addMoveToDeskTaskChanges_defaultToCenterIfFree() {
         setUpLandscapeDisplay()
         val stableBounds = Rect()
         displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1367,7 +1367,7 @@
 
         val task = setUpFullscreenTask()
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1375,7 +1375,7 @@
     }
 
     @Test
-    fun addMoveToDesktopChanges_excludeCaptionFromAppBounds_nonResizableLandscape() {
+    fun addMoveToDeskTaskChanges_excludeCaptionFromAppBounds_nonResizableLandscape() {
         setUpLandscapeDisplay()
         val task =
             setUpFullscreenTask(
@@ -1385,7 +1385,7 @@
         whenever(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(task)).thenReturn(true)
         val initialAspectRatio = calculateAspectRatio(task)
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         val captionInsets = getAppHeaderHeight(context)
@@ -1397,7 +1397,7 @@
     }
 
     @Test
-    fun addMoveToDesktopChanges_excludeCaptionFromAppBounds_nonResizablePortrait() {
+    fun addMoveToDeskTaskChanges_excludeCaptionFromAppBounds_nonResizablePortrait() {
         setUpLandscapeDisplay()
         val task =
             setUpFullscreenTask(
@@ -1407,7 +1407,7 @@
         whenever(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(task)).thenReturn(true)
         val initialAspectRatio = calculateAspectRatio(task)
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         val finalBounds = findBoundsChange(wct, task)
         val captionInsets = getAppHeaderHeight(context)
@@ -1445,29 +1445,29 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-    fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
+    fun addMoveToDeskTaskChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
         setUpLandscapeDisplay()
         val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-    fun addMoveToDesktopChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() {
+    fun addMoveToDeskTaskChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() {
         setUpLandscapeDisplay()
         val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-    fun addMoveToDesktopChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() {
+    fun addMoveToDeskTaskChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() {
         setUpLandscapeDisplay()
         val task =
             setUpFullscreenTask(
@@ -1476,36 +1476,36 @@
                 aspectRatioOverrideApplied = true,
             )
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-    fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
+    fun addMoveToDeskTaskChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
         setUpPortraitDisplay()
         val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-    fun addMoveToDesktopChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() {
+    fun addMoveToDeskTaskChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() {
         setUpPortraitDisplay()
         val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
-    fun addMoveToDesktopChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() {
+    fun addMoveToDeskTaskChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() {
         setUpPortraitDisplay()
         val task =
             setUpFullscreenTask(
@@ -1515,7 +1515,7 @@
                 aspectRatioOverrideApplied = true,
             )
         val wct = WindowContainerTransaction()
-        controller.addMoveToDesktopChanges(wct, task)
+        controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
 
         assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
     }
@@ -2824,7 +2824,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
     fun moveToNextDisplay_toDesktopInOtherDisplay_bringsExistingTasksToFront() {
         val transition = Binder()
         val sourceDeskId = 0
@@ -2856,7 +2855,6 @@
         Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
         Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
     )
-    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
     fun moveToNextDisplay_toDesktopInOtherDisplay_movesHomeAndWallpaperToFront() {
         val homeTask = setUpHomeTask(displayId = SECOND_DISPLAY)
         whenever(desktopWallpaperActivityTokenProvider.getToken(SECOND_DISPLAY))
@@ -3506,6 +3504,39 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun handleRequest_fullscreenTask_switchToDesktop_movesTaskToDesk() {
+        taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 5)
+        setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 5)
+        taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 5)
+
+        val fullscreenTask = createFullscreenTask()
+        val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+        assertNotNull(wct, "should handle request")
+        verify(desksOrganizer).moveTaskToDesk(wct = wct, deskId = 5, task = fullscreenTask)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun handleRequest_fullscreenTaskThatWasInactiveInDesk_tracksDeskDeactivation() {
+        // Set up and existing desktop task in an active desk.
+        val inactiveInDeskTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+        taskRepository.setDeskInactive(deskId = 0)
+
+        // Now the task is launching as fullscreen.
+        inactiveInDeskTask.configuration.windowConfiguration.windowingMode =
+            WINDOWING_MODE_FULLSCREEN
+        val transition = Binder()
+        val wct = controller.handleRequest(transition, createTransition(inactiveInDeskTask))
+
+        // Desk is deactivated.
+        assertNotNull(wct, "should handle request")
+        verify(desksTransitionsObserver)
+            .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId = 0))
+    }
+
+    @Test
     fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
         val homeTask = setUpHomeTask()
         val freeformTask = setUpFreeformTask()
@@ -3681,6 +3712,20 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun handleRequest_freeformTaskFromInactiveDesk_tracksDeskDeactivation() {
+        val deskId = 0
+        val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+        taskRepository.setDeskInactive(deskId = deskId)
+
+        val transition = Binder()
+        controller.handleRequest(transition, createTransition(freeformTask))
+
+        verify(desksTransitionsObserver)
+            .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+    }
+
+    @Test
     fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() {
         val freeformTask = setUpFreeformTask()
         markTaskHidden(freeformTask)
@@ -3928,6 +3973,24 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    fun handleRequest_recentsAnimationRunning_relaunchActiveTask_tracksDeskDeactivation() {
+        // Set up a visible freeform task
+        val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+        markTaskVisible(freeformTask)
+
+        // Mark recents animation running
+        recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING)
+
+        val transition = Binder()
+        controller.handleRequest(transition, createTransition(freeformTask))
+
+        desksTransitionsObserver.addPendingTransition(
+            DeskTransition.DeactivateDesk(transition, deskId = 0)
+        )
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
     fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() {
         val freeformTask = setUpFreeformTask()
@@ -4045,6 +4108,31 @@
     }
 
     @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+    )
+    fun handleRequest_systemUIActivityWithDisplayInFreeformTask_inDesktop_tracksDeskDeactivation() {
+        val deskId = 5
+        taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+        taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+        val systemUIPackageName =
+            context.resources.getString(com.android.internal.R.string.config_systemUi)
+        val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "")
+        val task =
+            setUpFreeformTask(displayId = DEFAULT_DISPLAY).apply {
+                baseActivity = baseComponent
+                isTopActivityNoDisplay = false
+            }
+
+        val transition = Binder()
+        controller.handleRequest(transition, createTransition(task))
+
+        verify(desksTransitionsObserver)
+            .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
     fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() {
         val freeformTask = setUpFreeformTask()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
index 607e6a4..14f9ffc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
@@ -126,7 +127,7 @@
     }
 
     @Test
-    public void onAnimationStart_setCornerAndShadowRadii() {
+    public void onAnimationStart_fadeInAnimator_setCornerAndShadowRadii() {
         mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
                 mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
         mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
@@ -143,7 +144,26 @@
     }
 
     @Test
-    public void onAnimationUpdate_setCornerAndShadowRadii() {
+    public void onAnimationStart_fadeOutAnimator_setCornerNoShadowRadii() {
+        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+                mMockFinishTransaction, PipAlphaAnimator.FADE_OUT);
+        mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipAlphaAnimator.start();
+            mPipAlphaAnimator.pause();
+        });
+
+        verify(mMockStartTransaction, atLeastOnce())
+                .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+        verify(mMockStartTransaction, never())
+                .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+        verify(mMockStartTransaction, atLeastOnce())
+                .setShadowRadius(eq(mTestLeash), eq(0f));
+    }
+
+    @Test
+    public void onAnimationUpdate_fadeInAnimator_setCornerAndShadowRadii() {
         mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
                 mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
         mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
@@ -160,7 +180,26 @@
     }
 
     @Test
-    public void onAnimationEnd_setCornerAndShadowRadii() {
+    public void onAnimationUpdate_fadeOutAnimator_setCornerNoShadowRadii() {
+        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+                mMockFinishTransaction, PipAlphaAnimator.FADE_OUT);
+        mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipAlphaAnimator.start();
+            mPipAlphaAnimator.pause();
+        });
+
+        verify(mMockAnimateTransaction, atLeastOnce())
+                .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+        verify(mMockAnimateTransaction, never())
+                .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+        verify(mMockAnimateTransaction, atLeastOnce())
+                .setShadowRadius(eq(mTestLeash), eq(0f));
+    }
+
+    @Test
+    public void onAnimationEnd_fadeInAnimator_setCornerAndShadowRadii() {
         mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
                 mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
         mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
@@ -177,6 +216,25 @@
     }
 
     @Test
+    public void onAnimationEnd_fadeOutAnimator_setCornerNoShadowRadii() {
+        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+                mMockFinishTransaction, PipAlphaAnimator.FADE_OUT);
+        mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipAlphaAnimator.start();
+            mPipAlphaAnimator.end();
+        });
+
+        verify(mMockFinishTransaction, atLeastOnce())
+                .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+        verify(mMockFinishTransaction, never())
+                .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+        verify(mMockFinishTransaction, atLeastOnce())
+                .setShadowRadius(eq(mTestLeash), eq(0f));
+    }
+
+    @Test
     public void onAnimationEnd_fadeInAnimator_leashVisibleAtEnd() {
         mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
                 mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
index 180a691..3f7f21e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.graphics.Rect
-import android.view.View
 import android.widget.FrameLayout
 import androidx.core.animation.AnimatorTestRule
 import androidx.test.core.app.ApplicationProvider.getApplicationContext
@@ -58,8 +57,8 @@
     private val bubbleRightDragZone =
         DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = Rect(200, 0, 280, 150))
 
-    private val dropTargetView: View
-        get() = container.getChildAt(0)
+    private val dropTargetView: DropTargetView
+        get() = container.getChildAt(0) as DropTargetView
 
     @Before
     fun setUp() {
@@ -238,8 +237,9 @@
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
             dropTargetManager.onDragEnded()
-            // advance the timer by 100ms so the animation doesn't complete
-            animatorTestRule.advanceTimeBy(100)
+            // advance the timer by 50ms so the animation doesn't complete
+            // needs to be < DropTargetManager.DROP_TARGET_ALPHA_OUT_DURATION
+            animatorTestRule.advanceTimeBy(50)
         }
         assertThat(container.childCount).isEqualTo(1)
 
@@ -320,10 +320,10 @@
     }
 
     private fun verifyDropTargetPosition(rect: Rect) {
-        assertThat(dropTargetView.scaleX).isEqualTo(rect.width())
-        assertThat(dropTargetView.scaleY).isEqualTo(rect.height())
-        assertThat(dropTargetView.translationX).isEqualTo(rect.exactCenterX())
-        assertThat(dropTargetView.translationY).isEqualTo(rect.exactCenterY())
+        assertThat(dropTargetView.getRect().left).isEqualTo(rect.left)
+        assertThat(dropTargetView.getRect().top).isEqualTo(rect.top)
+        assertThat(dropTargetView.getRect().right).isEqualTo(rect.right)
+        assertThat(dropTargetView.getRect().bottom).isEqualTo(rect.bottom)
     }
 
     private class FakeDragZoneChangedListener : DropTargetManager.DragZoneChangedListener {
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index 30e7a62..6f60d01 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -55,12 +55,20 @@
     }
 }
 
+SkColor invert(SkColor color) {
+    Lab lab = sRGBToLab(color);
+    lab.L = 100 - lab.L;
+    return LabToSRGB(lab, SkColorGetA(color));
+}
+
 SkColor transformColor(ColorTransform transform, SkColor color) {
     switch (transform) {
         case ColorTransform::Light:
             return makeLight(color);
         case ColorTransform::Dark:
             return makeDark(color);
+        case ColorTransform::Invert:
+            return invert(color);
         default:
             return color;
     }
@@ -80,19 +88,6 @@
 static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
     if (transform == ColorTransform::None) return;
 
-    if (transform == ColorTransform::Invert) {
-        auto filter = SkHighContrastFilter::Make(
-                {/* grayscale= */ false, SkHighContrastConfig::InvertStyle::kInvertLightness,
-                 /* contrast= */ 0.0f});
-
-        if (paint.getColorFilter()) {
-            paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter()));
-        } else {
-            paint.setColorFilter(filter);
-        }
-        return;
-    }
-
     SkColor newColor = transformColor(transform, paint.getColor());
     paint.setColor(newColor);
 
@@ -112,6 +107,22 @@
             paint.setShader(SkGradientShader::MakeLinear(
                     info.fPoints, info.fColors, info.fColorOffsets, info.fColorCount,
                     info.fTileMode, info.fGradientFlags, nullptr));
+        } else {
+            if (transform == ColorTransform::Invert) {
+                // Since we're trying to invert every thing around this draw call, we invert
+                // the color of the draw call if we don't know what it is.
+                auto filter = SkHighContrastFilter::Make(
+                        {/* grayscale= */ false,
+                         SkHighContrastConfig::InvertStyle::kInvertLightness,
+                         /* contrast= */ 0.0f});
+
+                if (paint.getColorFilter()) {
+                    paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter()));
+                } else {
+                    paint.setColorFilter(filter);
+                }
+                return;
+            }
         }
     }
 
@@ -150,8 +161,13 @@
 }
 
 bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette) {
-    palette = filterPalette(paint, palette);
     bool shouldInvert = false;
+    if (transform == ColorTransform::Invert && palette != BitmapPalette::Colorful) {
+        // When the transform is Invert we invert any image that is not deemed "colorful",
+        // regardless of calculated image brightness.
+        shouldInvert = true;
+    }
+    palette = filterPalette(paint, palette);
     if (palette == BitmapPalette::Light && transform == ColorTransform::Dark) {
         shouldInvert = true;
     }
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 4801bd1..8b4e59a 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -27,6 +27,7 @@
 
 #include "DamageAccumulator.h"
 #include "Debug.h"
+#include "FeatureFlags.h"
 #include "Properties.h"
 #include "TreeInfo.h"
 #include "VectorDrawable.h"
@@ -398,26 +399,32 @@
     deleteDisplayList(observer, info);
     mDisplayList = std::move(mStagingDisplayList);
     if (mDisplayList) {
-        WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info)};
+        WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info) ||
+                                                   (info && isForceInvertDark(*info))};
         mDisplayList.syncContents(syncData);
         handleForceDark(info);
     }
 }
 
+// Return true if the tree should use the force invert feature that inverts
+// the entire tree to darken it.
 inline bool RenderNode::isForceInvertDark(TreeInfo& info) {
-    return CC_UNLIKELY(
-             info.forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK);
+    return CC_UNLIKELY(info.forceDarkType ==
+                       android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK);
 }
 
+// Return true if the tree should use the force dark feature that selectively
+// darkens light nodes on the tree.
 inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) {
-    return CC_UNLIKELY(
-            info &&
-            (!info->disableForceDark || isForceInvertDark(*info)));
+    return CC_UNLIKELY(info && !info->disableForceDark);
 }
 
-
-
-void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
+void RenderNode::handleForceDark(TreeInfo *info) {
+    if (CC_UNLIKELY(view_accessibility_flags::force_invert_color() && info &&
+                    isForceInvertDark(*info))) {
+        mDisplayList.applyColorTransform(ColorTransform::Invert);
+        return;
+    }
     if (!shouldEnableForceDark(info)) {
         return;
     }
@@ -427,13 +434,7 @@
         children.push_back(node);
     });
     if (mDisplayList.hasText()) {
-        if (isForceInvertDark(*info) && mDisplayList.hasFill()) {
-            // Handle a special case for custom views that draw both text and background in the
-            // same RenderNode, which would otherwise be altered to white-on-white text.
-            usage = UsageHint::Container;
-        } else {
-            usage = UsageHint::Foreground;
-        }
+        usage = UsageHint::Foreground;
     }
     if (usage == UsageHint::Unknown) {
         if (children.size() > 1) {
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 63a024b..3ef9708 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -16,6 +16,8 @@
 #include "Bitmap.h"
 
 #include <android-base/file.h>
+
+#include "FeatureFlags.h"
 #include "HardwareBitmapUploader.h"
 #include "Properties.h"
 #ifdef __ANDROID__  // Layoutlib does not support render thread
@@ -547,9 +549,16 @@
     }
 
     ALOGV("samples = %d, hue [min = %f, max = %f, avg = %f]; saturation [min = %f, max = %f, avg = "
-          "%f]",
+          "%f] %d x %d",
           sampledCount, hue.min(), hue.max(), hue.average(), saturation.min(), saturation.max(),
-          saturation.average());
+          saturation.average(), info.width(), info.height());
+
+    if (CC_UNLIKELY(view_accessibility_flags::force_invert_color())) {
+        if (saturation.delta() > 0.1f ||
+            (hue.delta() > 20 && saturation.average() > 0.2f && value.average() < 0.9f)) {
+            return BitmapPalette::Colorful;
+        }
+    }
 
     if (hue.delta() <= 20 && saturation.delta() <= .1f) {
         if (value.average() >= .5f) {
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 4e9bcf2..0fe5fe8 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -49,6 +49,7 @@
     Unknown,
     Light,
     Dark,
+    Colorful,
 };
 
 namespace uirenderer {
diff --git a/libs/hwui/jni/GIFMovie.cpp b/libs/hwui/jni/GIFMovie.cpp
index 6c82aa1..476b6fd 100644
--- a/libs/hwui/jni/GIFMovie.cpp
+++ b/libs/hwui/jni/GIFMovie.cpp
@@ -63,7 +63,7 @@
     }
     fCurrIndex = -1;
     fLastDrawIndex = -1;
-    fPaintingColor = SkPackARGB32(0, 0, 0, 0);
+    fPaintingColor = SK_AlphaTRANSPARENT;
 }
 
 GIFMovie::~GIFMovie()
@@ -127,7 +127,7 @@
     for (; width > 0; width--, src++, dst++) {
         if (*src != transparent && *src < cmap->ColorCount) {
             const GifColorType& col = cmap->Colors[*src];
-            *dst = SkPackARGB32(0xFF, col.Red, col.Green, col.Blue);
+            *dst = SkColorSetRGB(col.Red, col.Green, col.Blue);
         }
     }
 }
@@ -395,10 +395,10 @@
         lastIndex = fGIF->ImageCount - 1;
     }
 
-    SkColor bgColor = SkPackARGB32(0, 0, 0, 0);
+    SkColor bgColor = SK_ColorTRANSPARENT;
     if (gif->SColorMap != nullptr && gif->SBackGroundColor < gif->SColorMap->ColorCount) {
         const GifColorType& col = gif->SColorMap->Colors[gif->SBackGroundColor];
-        bgColor = SkColorSetARGB(0xFF, col.Red, col.Green, col.Blue);
+        bgColor = SkColorSetRGB(col.Red, col.Green, col.Blue);
     }
 
     // draw each frames - not intelligent way
@@ -411,7 +411,7 @@
             if (!trans && gif->SColorMap != nullptr) {
                 fPaintingColor = bgColor;
             } else {
-                fPaintingColor = SkColorSetARGB(0, 0, 0, 0);
+                fPaintingColor = SK_ColorTRANSPARENT;
             }
 
             bm->eraseColor(fPaintingColor);
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 80b55e2..5a993bf 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -33,6 +33,7 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.pm.PackageManager;
+import android.location.flags.Flags;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -181,7 +182,8 @@
     public static final int POWER_HIGH = 203;
 
     private static final long IMPLICIT_MIN_UPDATE_INTERVAL = -1;
-    private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D;
+    private static final double LEGACY_IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D;
+    private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 2D;
 
     private @Nullable String mProvider;
     private @Quality int mQuality;
@@ -553,7 +555,10 @@
      */
     public @IntRange(from = 0) long getMinUpdateIntervalMillis() {
         if (mMinUpdateIntervalMillis == IMPLICIT_MIN_UPDATE_INTERVAL) {
-            return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
+            if (Flags.updateMinLocationRequestInterval()) {
+                return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
+            }
+            return (long) (mIntervalMillis * LEGACY_IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
         } else {
             // the min is only necessary in case someone use a deprecated function to mess with the
             // interval or min update interval
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 1b38982..83b1778 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -168,3 +168,14 @@
     description: "Flag for GNSS assistance interface"
     bug: "209078566"
 }
+
+flag {
+    name: "update_min_location_request_interval"
+    namespace: "location"
+    description: "Flag for updating the default logic for the minimal interval for location request"
+    bug: "397444378"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index 0ad7f5f..3d011bc 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -76,6 +76,7 @@
                         android:layout_width="match_parent"
                         android:layout_height="match_parent"
                         android:visibility="gone"
+                        android:accessibilityLiveRegion="polite"
                         style="@style/TimeoutMessage" />
 
                     <androidx.recyclerview.widget.RecyclerView
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index dd77c61..b2c1e60 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -339,7 +339,7 @@
     private void onDiscoveryStateChanged(DiscoveryState newState) {
         switch (newState) {
             case IN_PROGRESS: {
-                mTimeoutMessage.setText(null);
+                mTimeoutMessage.setVisibility(View.GONE);
                 mProgressBar.setIndeterminate(true);
                 break;
             }
@@ -351,6 +351,7 @@
                         R.string.message_discovery_soft_timeout,
                         deviceType, discoveryType, profile);
                 mTimeoutMessage.setText(message);
+                mTimeoutMessage.setVisibility(View.VISIBLE);
                 break;
             }
             case FINISHED_STOPPED: {
@@ -363,6 +364,7 @@
                         }
                     }
                     mTimeoutMessage.setText(getString(R.string.message_discovery_hard_timeout));
+                    mTimeoutMessage.setVisibility(View.VISIBLE);
                 }
                 mProgressBar.setIndeterminate(false);
                 break;
@@ -528,6 +530,7 @@
         mVendorHeader.setVisibility(View.VISIBLE);
         mProfileIcon.setVisibility(View.GONE);
         mDeviceListRecyclerView.setVisibility(View.GONE);
+        mTimeoutMessage.setVisibility(View.GONE);
         mProgressBar.setVisibility(View.GONE);
         mBorderBottom.setVisibility(View.GONE);
     }
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
index d8348d1..8f12cb4 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
@@ -101,7 +101,7 @@
 
     void setDevices(List<DeviceFilterPair<?>> devices) {
         mDevices = devices;
-        notifyDataSetChanged();
+        notifyItemRangeInserted(devices.size(), mDevices.size());
     }
 
     static class ViewHolder extends RecyclerView.ViewHolder {
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt b/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt
index 8214c54..a4803d2 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt
@@ -44,7 +44,7 @@
             if (enabled)
                 listOf(
                         "---- AUTOPILOT ENGAGED ----",
-                        "TGT: " + (target?.name?.toUpperCase() ?: "SELECTING..."),
+                        "TGT: " + (target?.name?.uppercase() ?: "SELECTING..."),
                         "EXE: $strategy" + if (debug.isNotEmpty()) " ($debug)" else "",
                     )
                     .joinToString("\n")
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
index 95a60c7..61ea08e 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
@@ -236,8 +236,8 @@
                                     ((closest.pos - pos).mag() - closest.radius).toInt()
                                 listOfNotNull(
                                         landing?.let {
-                                            "LND: ${it.planet.name.toUpperCase()}\n" +
-                                                "JOB: ${it.text.toUpperCase()}"
+                                            "LND: ${it.planet.name.uppercase()}\n" +
+                                                "JOB: ${it.text.uppercase()}"
                                         }
                                             ?: if (distToClosest < 10_000) {
                                                 "ALT: $distToClosest"
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index f692601e..3c70fc1 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -96,6 +96,7 @@
         Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
         Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
         Settings.Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION,
+        Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK,
         Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
         Settings.Secure.PREFERRED_TTY_MODE,
         Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index e42d3fb..c09e45e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -147,6 +147,7 @@
         VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, ANY_STRING_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_LARGE_POINTER_ICON, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 Secure.PREFERRED_TTY_MODE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index c29a5a2..167674a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1722,6 +1722,9 @@
                 Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT,
                 SecureSettingsProto.Accessibility.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT);
         dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK,
+                SecureSettingsProto.Accessibility.AUTOCLICK_REVERT_TO_LEFT_CLICK);
+        dumpSetting(s, p,
                 Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
                 SecureSettingsProto.Accessibility.AUTOCLICK_ENABLED);
         dumpSetting(s, p,
diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS
index 2fa707e..897c1fe 100644
--- a/packages/Shell/OWNERS
+++ b/packages/Shell/OWNERS
@@ -4,7 +4,6 @@
 jsharkey@android.com
 felipeal@google.com
 nandana@google.com
-svetoslavganov@google.com
 hackbod@google.com
 yamasani@google.com
 patb@google.com
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/BundleHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/BundleHeader.kt
new file mode 100644
index 0000000..d7740a4
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/BundleHeader.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2025 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.notifications.ui.composable.row
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.constrainHeight
+import androidx.compose.ui.unit.constrainWidth
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastMap
+import androidx.compose.ui.util.fastMaxOfOrDefault
+import androidx.compose.ui.util.fastSumBy
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.theme.PlatformTheme
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModel
+
+object BundleHeader {
+    object Scenes {
+        val Collapsed = SceneKey("Collapsed")
+        val Expanded = SceneKey("Expanded")
+    }
+
+    object Elements {
+        val PreviewIcon1 = ElementKey("PreviewIcon1")
+        val PreviewIcon2 = ElementKey("PreviewIcon2")
+        val PreviewIcon3 = ElementKey("PreviewIcon3")
+        val TitleText = ElementKey("TitleText")
+    }
+}
+
+fun createComposeView(viewModel: BundleHeaderViewModel, context: Context): ComposeView {
+    // TODO(b/399588047): Check if we can init PlatformTheme once instead of once per ComposeView
+    return ComposeView(context).apply { setContent { PlatformTheme { BundleHeader(viewModel) } } }
+}
+
+@Composable
+fun BundleHeader(viewModel: BundleHeaderViewModel, modifier: Modifier = Modifier) {
+    Box(modifier) {
+        Background(background = viewModel.backgroundDrawable, modifier = Modifier.matchParentSize())
+        val scope = rememberCoroutineScope()
+        SceneTransitionLayout(
+            state = viewModel.state,
+            modifier =
+                Modifier.clickable(
+                    onClick = { viewModel.onHeaderClicked(scope) },
+                    interactionSource = null,
+                    indication = null,
+                ),
+        ) {
+            scene(BundleHeader.Scenes.Collapsed) {
+                BundleHeaderContent(viewModel, collapsed = true)
+            }
+            scene(BundleHeader.Scenes.Expanded) {
+                BundleHeaderContent(viewModel, collapsed = false)
+            }
+        }
+    }
+}
+
+@Composable
+private fun Background(background: Drawable?, modifier: Modifier = Modifier) {
+    if (background != null) {
+        val painter = rememberDrawablePainter(drawable = background)
+        Image(
+            painter = painter,
+            contentDescription = null,
+            contentScale = ContentScale.Crop,
+            modifier = modifier,
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+private fun ContentScope.BundleHeaderContent(
+    viewModel: BundleHeaderViewModel,
+    collapsed: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    Row(
+        verticalAlignment = Alignment.CenterVertically,
+        modifier = modifier.padding(vertical = 16.dp),
+    ) {
+        BundleIcon(viewModel.bundleIcon, modifier = Modifier.padding(horizontal = 16.dp))
+        Text(
+            text = viewModel.titleText,
+            style = MaterialTheme.typography.titleMediumEmphasized,
+            color = MaterialTheme.colorScheme.primary,
+            overflow = TextOverflow.Ellipsis,
+            maxLines = 1,
+            modifier = Modifier.element(BundleHeader.Elements.TitleText).weight(1f),
+        )
+
+        if (collapsed && viewModel.previewIcons.isNotEmpty()) {
+            BundlePreviewIcons(
+                previewDrawables = viewModel.previewIcons,
+                modifier = Modifier.padding(start = 8.dp),
+            )
+        }
+
+        ExpansionControl(
+            collapsed = collapsed,
+            hasUnread = viewModel.hasUnreadMessages,
+            numberToShow = viewModel.numberOfChildren,
+            modifier = Modifier.padding(start = 8.dp, end = 16.dp),
+        )
+    }
+}
+
+@Composable
+private fun ContentScope.BundlePreviewIcons(
+    previewDrawables: List<Drawable>,
+    modifier: Modifier = Modifier,
+) {
+    check(previewDrawables.isNotEmpty())
+    val iconSize = 32.dp
+    HalfOverlappingReversedRow(modifier = modifier) {
+        PreviewIcon(
+            drawable = previewDrawables[0],
+            modifier = Modifier.element(BundleHeader.Elements.PreviewIcon1).size(iconSize),
+        )
+        if (previewDrawables.size < 2) return@HalfOverlappingReversedRow
+        PreviewIcon(
+            drawable = previewDrawables[1],
+            modifier = Modifier.element(BundleHeader.Elements.PreviewIcon2).size(iconSize),
+        )
+        if (previewDrawables.size < 3) return@HalfOverlappingReversedRow
+        PreviewIcon(
+            drawable = previewDrawables[2],
+            modifier = Modifier.element(BundleHeader.Elements.PreviewIcon3).size(iconSize),
+        )
+    }
+}
+
+@Composable
+private fun HalfOverlappingReversedRow(
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit,
+) {
+    Layout(modifier = modifier, content = content) { measurables, constraints ->
+        val placeables = measurables.fastMap { measurable -> measurable.measure(constraints) }
+
+        if (placeables.isEmpty())
+            return@Layout layout(constraints.minWidth, constraints.minHeight) {}
+        val width = placeables.fastSumBy { it.width / 2 } + placeables.first().width / 2
+        val childHeight = placeables.fastMaxOfOrDefault(0) { it.height }
+
+        layout(constraints.constrainWidth(width), constraints.constrainHeight(childHeight)) {
+            // Start in the middle of the right-most placeable
+            var currentXPosition = placeables.fastSumBy { it.width / 2 }
+            placeables.fastForEach { placeable ->
+                currentXPosition -= placeable.width / 2
+                placeable.placeRelative(x = currentXPosition, y = 0)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowPrimitives.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowPrimitives.kt
new file mode 100644
index 0000000..c9ffa40
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowPrimitives.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2025 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.notifications.ui.composable.row
+
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ExpandMore
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.LowestZIndexContentPicker
+import com.android.compose.animation.scene.ValueKey
+import com.android.compose.animation.scene.animateElementColorAsState
+import com.android.compose.animation.scene.animateElementFloatAsState
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+
+object NotificationRowPrimitives {
+    object Elements {
+        val PillBackground = ElementKey("PillBackground", contentPicker = LowestZIndexContentPicker)
+        val NotificationIconBackground = ElementKey("NotificationIconBackground")
+        val Chevron = ElementKey("Chevron")
+    }
+
+    object Values {
+        val ChevronRotation = ValueKey("NotificationChevronRotation")
+        val PillBackgroundColor = ValueKey("PillBackgroundColor")
+    }
+}
+
+/** The Icon displayed at the start of any notification row. */
+@Composable
+fun ContentScope.BundleIcon(drawable: Drawable?, modifier: Modifier = Modifier) {
+    val surfaceColor = notificationElementSurfaceColor()
+    Box(
+        modifier =
+            modifier
+                // Has to be a shared element because we may have semi-transparent background color
+                .element(NotificationRowPrimitives.Elements.NotificationIconBackground)
+                .size(40.dp)
+                .background(color = surfaceColor, shape = CircleShape)
+    ) {
+        if (drawable == null) return@Box
+        val painter = rememberDrawablePainter(drawable)
+        Image(
+            painter = painter,
+            contentDescription = null,
+            modifier = Modifier.padding(10.dp).fillMaxSize(),
+            contentScale = ContentScale.Fit,
+            colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary),
+        )
+    }
+}
+
+/** The Icon used to display a preview of contained child notifications in a Bundle. */
+@Composable
+fun PreviewIcon(drawable: Drawable, modifier: Modifier = Modifier) {
+    val surfaceColor = notificationElementSurfaceColor()
+    Box(
+        modifier =
+            modifier
+                .background(color = surfaceColor, shape = CircleShape)
+                .border(0.5.dp, surfaceColor, CircleShape)
+    ) {
+        val painter = rememberDrawablePainter(drawable)
+        Image(
+            painter = painter,
+            contentDescription = null,
+            modifier = Modifier.fillMaxSize().clip(CircleShape),
+            contentScale = ContentScale.Fit,
+        )
+    }
+}
+
+/** The ExpansionControl of any expandable notification row, containing a Chevron. */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun ContentScope.ExpansionControl(
+    collapsed: Boolean,
+    hasUnread: Boolean,
+    numberToShow: Int?,
+    modifier: Modifier = Modifier,
+) {
+    val textColor =
+        if (hasUnread) MaterialTheme.colorScheme.onTertiary else MaterialTheme.colorScheme.onSurface
+    Box(modifier = modifier) {
+        // The background is a shared Element and therefore can't be the parent of a different
+        // shared Element (the chevron), otherwise the child can't be animated.
+        PillBackground(hasUnread, modifier = Modifier.matchParentSize())
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            modifier = Modifier.padding(vertical = 2.dp, horizontal = 6.dp),
+        ) {
+            val iconSizeDp = with(LocalDensity.current) { 16.sp.toDp() }
+
+            if (numberToShow != null) {
+                Text(
+                    text = numberToShow.toString(),
+                    style = MaterialTheme.typography.labelSmallEmphasized,
+                    color = textColor,
+                    modifier = Modifier.padding(end = 2.dp),
+                )
+            }
+            Chevron(collapsed = collapsed, modifier = Modifier.size(iconSizeDp), color = textColor)
+        }
+    }
+}
+
+@Composable
+private fun ContentScope.PillBackground(hasUnread: Boolean, modifier: Modifier = Modifier) {
+    ElementWithValues(NotificationRowPrimitives.Elements.PillBackground, modifier) {
+        val bgColorNoUnread = notificationElementSurfaceColor()
+        val surfaceColor by
+            animateElementColorAsState(
+                if (hasUnread) MaterialTheme.colorScheme.tertiary else bgColorNoUnread,
+                NotificationRowPrimitives.Values.PillBackgroundColor,
+            )
+        content {
+            Box(
+                modifier =
+                    Modifier.drawBehind {
+                        drawRoundRect(
+                            color = surfaceColor,
+                            cornerRadius = CornerRadius(100.dp.toPx(), 100.dp.toPx()),
+                        )
+                    }
+            )
+        }
+    }
+}
+
+@Composable
+@ReadOnlyComposable
+private fun notificationElementSurfaceColor(): Color {
+    return if (isSystemInDarkTheme()) {
+        Color.White.copy(alpha = 0.15f)
+    } else {
+        MaterialTheme.colorScheme.surfaceContainerHighest
+    }
+}
+
+@Composable
+private fun ContentScope.Chevron(collapsed: Boolean, color: Color, modifier: Modifier = Modifier) {
+    val key = NotificationRowPrimitives.Elements.Chevron
+    ElementWithValues(key, modifier) {
+        val rotation by
+            animateElementFloatAsState(
+                if (collapsed) 0f else 180f,
+                NotificationRowPrimitives.Values.ChevronRotation,
+            )
+        content {
+            Icon(
+                imageVector = Icons.Default.ExpandMore,
+                contentDescription = null,
+                modifier = Modifier.graphicsLayer { rotationZ = rotation },
+                tint = color,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index b59b4ab..0648412 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -103,6 +103,7 @@
         fun onAvailableClocksChanged() {}
     }
 
+    private val replacementMap = ConcurrentHashMap<ClockId, ClockId>()
     private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
     private val clockChangeListeners = mutableListOf<ClockChangeListener>()
     private val settingObserver =
@@ -209,6 +210,7 @@
                         continue
                     }
 
+                    clock.replacementTarget?.let { replacementMap[id] = it }
                     info.provider = plugin
                     onLoaded(info)
                 }
@@ -393,10 +395,11 @@
     // TODO: Merge w/ CurrentClockId when we convert to a flow. We shouldn't need both behaviors.
     val activeClockId: String
         get() {
-            if (!availableClocks.containsKey(currentClockId)) {
+            var id = currentClockId
+            if (!availableClocks.containsKey(id)) {
                 return DEFAULT_CLOCK_ID
             }
-            return currentClockId
+            return replacementMap[id] ?: id
         }
 
     init {
@@ -404,6 +407,7 @@
         defaultClockProvider.initialize(clockBuffers)
         for (clock in defaultClockProvider.getClocks()) {
             availableClocks[clock.clockId] = ClockInfo(clock, defaultClockProvider, null)
+            clock.replacementTarget?.let { replacementMap[clock.clockId] = it }
         }
 
         // Something has gone terribly wrong if the default clock isn't present
@@ -562,9 +566,12 @@
         }
     }
 
-    fun getClocks(): List<ClockMetadata> {
-        if (!isEnabled) return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
-        return availableClocks.map { (_, clock) -> clock.metadata }
+    fun getClocks(includeDeprecated: Boolean = false): List<ClockMetadata> {
+        return when {
+            !isEnabled -> listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
+            includeDeprecated -> availableClocks.map { (_, clock) -> clock.metadata }
+            else -> availableClocks.map { (_, clock) -> clock.metadata }.filter { !it.isDeprecated }
+        }
     }
 
     fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
index b9200c1..e9e61a7 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
@@ -44,6 +44,7 @@
     val dozeState = DefaultClockController.AnimationState(1F)
 
     override val view = FlexClockView(clockCtx)
+    override var onViewBoundsChanged by view::onViewBoundsChanged
 
     init {
         fun createController(cfg: LayerConfig) {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index f24ad1c..9bb3bac 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -17,6 +17,7 @@
 import android.content.res.Resources
 import android.graphics.Color
 import android.graphics.Rect
+import android.graphics.RectF
 import android.icu.text.NumberFormat
 import android.util.TypedValue
 import android.view.LayoutInflater
@@ -97,7 +98,12 @@
         events.onLocaleChanged(Locale.getDefault())
     }
 
-    override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) {
+    override fun initialize(
+        isDarkTheme: Boolean,
+        dozeFraction: Float,
+        foldFraction: Float,
+        onBoundsChanged: (RectF) -> Unit,
+    ) {
         largeClock.recomputePadding(null)
 
         largeClock.animations = LargeClockAnimations(largeClock.view, dozeFraction, foldFraction)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 654478a..c3935e6 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -61,7 +61,14 @@
 
     override fun getClocks(): List<ClockMetadata> {
         var clocks = listOf(ClockMetadata(DEFAULT_CLOCK_ID))
-        if (isClockReactiveVariantsEnabled) clocks += ClockMetadata(FLEX_CLOCK_ID)
+        if (isClockReactiveVariantsEnabled) {
+            clocks +=
+                ClockMetadata(
+                    FLEX_CLOCK_ID,
+                    isDeprecated = true,
+                    replacementTarget = DEFAULT_CLOCK_ID,
+                )
+        }
         return clocks
     }
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
index 1a1033b..6dfd226 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shared.clocks
 
+import android.graphics.RectF
 import com.android.systemui.animation.GSFAxes
 import com.android.systemui.customization.R
 import com.android.systemui.plugins.clocks.AlarmData
@@ -102,9 +103,15 @@
             }
         }
 
-    override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) {
+    override fun initialize(
+        isDarkTheme: Boolean,
+        dozeFraction: Float,
+        foldFraction: Float,
+        onBoundsChanged: (RectF) -> Unit,
+    ) {
         events.onFontAxesChanged(clockCtx.settings.axes)
         smallClock.run {
+            layerController.onViewBoundsChanged = onBoundsChanged
             events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme))
             animations.doze(dozeFraction)
             animations.fold(foldFraction)
@@ -112,6 +119,7 @@
         }
 
         largeClock.run {
+            layerController.onViewBoundsChanged = onBoundsChanged
             events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme))
             animations.doze(dozeFraction)
             animations.fold(foldFraction)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index 2282863..578a489 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -247,7 +247,7 @@
                 timespec = DigitalTimespec.TIME_FULL_FORMAT,
                 style = FontTextStyle(fontSizeScale = 0.98f),
                 aodStyle = FontTextStyle(),
-                alignment = DigitalAlignment(HorizontalAlignment.LEFT, VerticalAlignment.CENTER),
+                alignment = DigitalAlignment(HorizontalAlignment.START, VerticalAlignment.CENTER),
                 dateTimeFormat = "h:mm",
             )
     }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
index af00cc2..336c66e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shared.clocks
 
+import android.graphics.RectF
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.plugins.clocks.ClockAnimations
@@ -31,4 +32,5 @@
     val config: ClockFaceConfig
 
     @VisibleForTesting var fakeTimeMills: Long?
+    var onViewBoundsChanged: ((RectF) -> Unit)?
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
index 0b7ea1a..97004ef 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
@@ -85,6 +85,7 @@
     override val view = SimpleDigitalClockTextView(clockCtx, isLargeClock)
     private val logger = Logger(clockCtx.messageBuffer, TAG)
     val timespec = DigitalTimespecHandler(layerCfg.timespec, layerCfg.dateTimeFormat)
+    override var onViewBoundsChanged by view::onViewBoundsChanged
 
     @VisibleForTesting
     override var fakeTimeMills: Long?
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index 2d0ca53..c765ea9 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -100,6 +100,7 @@
         updateLocale(Locale.getDefault())
     }
 
+    var onViewBoundsChanged: ((RectF) -> Unit)? = null
     private val digitOffsets = mutableMapOf<Int, Float>()
 
     protected fun calculateSize(
@@ -189,13 +190,21 @@
 
     fun updateLocation() {
         val layoutBounds = this.layoutBounds ?: return
+        val bounds =
+            RectF(
+                layoutBounds.centerX() - measuredWidth / 2f,
+                layoutBounds.centerY() - measuredHeight / 2f,
+                layoutBounds.centerX() + measuredWidth / 2f,
+                layoutBounds.centerY() + measuredHeight / 2f,
+            )
         setFrame(
-            (layoutBounds.centerX() - measuredWidth / 2f).roundToInt(),
-            (layoutBounds.centerY() - measuredHeight / 2f).roundToInt(),
-            (layoutBounds.centerX() + measuredWidth / 2f).roundToInt(),
-            (layoutBounds.centerY() + measuredHeight / 2f).roundToInt(),
+            bounds.left.roundToInt(),
+            bounds.top.roundToInt(),
+            bounds.right.roundToInt(),
+            bounds.bottom.roundToInt(),
         )
         updateChildFrames(isLayout = false)
+        onViewBoundsChanged?.let { it(bounds) }
     }
 
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index 015a827..abe5cc2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -31,6 +31,7 @@
 import android.util.Log
 import android.util.MathUtils.lerp
 import android.util.TypedValue
+import android.view.View
 import android.view.View.MeasureSpec.EXACTLY
 import android.view.animation.Interpolator
 import android.view.animation.PathInterpolator
@@ -74,14 +75,38 @@
 enum class VerticalAlignment {
     TOP,
     BOTTOM,
-    BASELINE, // default
+    BASELINE,
     CENTER,
 }
 
 enum class HorizontalAlignment {
+    LEFT {
+        override fun resolveXAlignment(view: View) = XAlignment.LEFT
+    },
+    RIGHT {
+        override fun resolveXAlignment(view: View) = XAlignment.RIGHT
+    },
+    START {
+        override fun resolveXAlignment(view: View): XAlignment {
+            return if (view.isLayoutRtl()) XAlignment.RIGHT else XAlignment.LEFT
+        }
+    },
+    END {
+        override fun resolveXAlignment(view: View): XAlignment {
+            return if (view.isLayoutRtl()) XAlignment.LEFT else XAlignment.RIGHT
+        }
+    },
+    CENTER {
+        override fun resolveXAlignment(view: View) = XAlignment.CENTER
+    };
+
+    abstract fun resolveXAlignment(view: View): XAlignment
+}
+
+enum class XAlignment {
     LEFT,
     RIGHT,
-    CENTER, // default
+    CENTER,
 }
 
 @SuppressLint("AppCompatCustomView")
@@ -117,6 +142,7 @@
         fidgetFontVariation = buildFidgetVariation(lsFontAxes).toFVar()
     }
 
+    var onViewBoundsChanged: ((RectF) -> Unit)? = null
     private val parser = DimensionParser(clockCtx.context)
     var maxSingleDigitHeight = -1f
     var maxSingleDigitWidth = -1f
@@ -154,7 +180,11 @@
     }
 
     var verticalAlignment: VerticalAlignment = VerticalAlignment.BASELINE
-    var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.LEFT
+    var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.CENTER
+
+    val xAlignment: XAlignment
+        get() = horizontalAlignment.resolveXAlignment(this)
+
     var isAnimationEnabled = true
     var dozeFraction: Float = 0f
         set(value) {
@@ -256,6 +286,7 @@
         canvas.use {
             digitTranslateAnimator?.apply { canvas.translate(currentTranslation) }
             canvas.translate(getDrawTranslation(interpBounds))
+            if (isLayoutRtl()) canvas.translate(interpBounds.width() - textBounds.width(), 0f)
             textAnimator.draw(canvas)
         }
     }
@@ -456,16 +487,16 @@
     private fun setInterpolatedLocation(measureSize: VPointF): RectF {
         val targetRect = RectF()
         targetRect.apply {
-            when (horizontalAlignment) {
-                HorizontalAlignment.LEFT -> {
+            when (xAlignment) {
+                XAlignment.LEFT -> {
                     left = layoutBounds.left
                     right = layoutBounds.left + measureSize.x
                 }
-                HorizontalAlignment.CENTER -> {
+                XAlignment.CENTER -> {
                     left = layoutBounds.centerX() - measureSize.x / 2f
                     right = layoutBounds.centerX() + measureSize.x / 2f
                 }
-                HorizontalAlignment.RIGHT -> {
+                XAlignment.RIGHT -> {
                     left = layoutBounds.right - measureSize.x
                     right = layoutBounds.right
                 }
@@ -497,6 +528,7 @@
             targetRect.right.roundToInt(),
             targetRect.bottom.roundToInt(),
         )
+        onViewBoundsChanged?.let { it(targetRect) }
         return targetRect
     }
 
@@ -504,10 +536,10 @@
         val sizeDiff = this.measuredSize - interpBounds.size
         val alignment =
             VPointF(
-                when (horizontalAlignment) {
-                    HorizontalAlignment.LEFT -> 0f
-                    HorizontalAlignment.CENTER -> 0.5f
-                    HorizontalAlignment.RIGHT -> 1f
+                when (xAlignment) {
+                    XAlignment.LEFT -> 0f
+                    XAlignment.CENTER -> 0.5f
+                    XAlignment.RIGHT -> 1f
                 },
                 when (verticalAlignment) {
                     VerticalAlignment.TOP -> 0f
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 189d554..f4d4b1e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -25,6 +25,8 @@
 import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.View
+import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.OnPreDrawListener
 import android.view.WindowInsetsController
 import android.widget.FrameLayout
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -720,6 +722,37 @@
     }
 
     @Test
+    fun startAppearAnimation_ifDelayed() {
+        val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
+        whenever(view.isAppearAnimationDelayed).thenReturn(true)
+        val viewTreeObserver: ViewTreeObserver = mock()
+        whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
+
+        underTest.startAppearAnimationIfDelayed()
+
+        verify(view).alpha = 1f
+        verify(viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
+        argumentCaptor.value.onPreDraw()
+
+        verify(view).startAppearAnimation(any(SecurityMode::class.java))
+        verify(view).setIsAppearAnimationDelayed(false)
+    }
+
+    @Test
+    fun appearAnimation_willNotStart_ifNotDelayed() {
+        whenever(view.isAppearAnimationDelayed).thenReturn(false)
+        val viewTreeObserver: ViewTreeObserver = mock()
+        whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
+
+        underTest.startAppearAnimationIfDelayed()
+
+        verify(view, never()).alpha
+        verify(viewTreeObserver, never()).addOnPreDrawListener(any())
+
+        verify(view, never()).startAppearAnimation(any(SecurityMode::class.java))
+    }
+
+    @Test
     fun gravityReappliedOnConfigurationChange() {
         // Set initial gravity
         testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 176824f..2845f6a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -452,6 +452,14 @@
         verify(keyguardPasswordView).setDisappearAnimationListener(any());
     }
 
+    @Test
+    public void setupForDelayedAppear() {
+        mKeyguardSecurityContainer.setupForDelayedAppear();
+        assertThat(mKeyguardSecurityContainer.getTranslationY()).isEqualTo(0f);
+        assertThat(mKeyguardSecurityContainer.getAlpha()).isEqualTo(0f);
+        assertThat(mKeyguardSecurityContainer.isAppearAnimationDelayed()).isTrue();
+    }
+
     private BackEvent createBackEvent(float touchX, float progress) {
         return new BackEvent(0, 0, progress, BackEvent.EDGE_LEFT);
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
index f53f964..191eccc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -28,6 +28,8 @@
 
 import android.animation.ValueAnimator;
 import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
@@ -54,6 +56,7 @@
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 import org.junit.Before;
@@ -127,10 +130,16 @@
     @Mock
     WindowRootView mWindowRootView;
 
+    @Mock
+    Resources mResources;
+
     private SceneInteractor mSceneInteractor;
 
+    private KeyguardStateController mKeyguardStateController;
+
     private static final float TOUCH_REGION = .3f;
     private static final float MIN_BOUNCER_HEIGHT = .05f;
+    private final Configuration mConfiguration = new Configuration();
 
     private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
     private static final UserInfo CURRENT_USER_INFO = new UserInfo(
@@ -153,6 +162,8 @@
     public void setup() {
         mKosmos = new KosmosJavaAdapter(this);
         mSceneInteractor = spy(mKosmos.getSceneInteractor());
+        mKeyguardStateController = mKosmos.getKeyguardStateController();
+        mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT;
 
         MockitoAnnotations.initMocks(this);
         mTouchHandler = new BouncerSwipeTouchHandler(
@@ -172,7 +183,9 @@
                 mKeyguardInteractor,
                 mSceneInteractor,
                 mKosmos.getShadeRepository(),
-                Optional.of(() -> mWindowRootView));
+                Optional.of(() -> mWindowRootView),
+                mKeyguardStateController,
+                mKosmos.getCommunalSettingsInteractor());
 
         when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
         when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
@@ -180,6 +193,9 @@
         when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
         when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
         when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false));
+        when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true);
+        when(mWindowRootView.getResources()).thenReturn(mResources);
+        when(mResources.getConfiguration()).thenReturn(mConfiguration);
     }
 
     /**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index dd43d81..e8dc6762 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.ambient.touch;
 
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
+
+import static com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
@@ -34,6 +38,8 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.platform.test.annotations.DisableFlags;
@@ -63,6 +69,7 @@
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 import org.junit.Before;
@@ -132,12 +139,16 @@
     @Mock
     WindowRootView mWindowRootView;
 
+    Resources mResources;
+
     @Mock
     CommunalViewModel mCommunalViewModel;
 
     @Mock
     KeyguardInteractor mKeyguardInteractor;
 
+    private KeyguardStateController mKeyguardStateController;
+
     @Captor
     ArgumentCaptor<Rect> mRectCaptor;
 
@@ -147,6 +158,7 @@
     private static final int SCREEN_WIDTH_PX = 1024;
     private static final int SCREEN_HEIGHT_PX = 100;
     private static final float MIN_BOUNCER_HEIGHT = .05f;
+    private final Configuration mConfiguration = new Configuration();
 
     private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
     private static final UserInfo CURRENT_USER_INFO = new UserInfo(
@@ -157,7 +169,8 @@
 
     @Parameters(name = "{0}")
     public static List<FlagsParameterization> getParams() {
-        return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
+        return SceneContainerFlagParameterizationKt
+                .andSceneContainer(allCombinationsOf(Flags.FLAG_GLANCEABLE_HUB_V2));
     }
 
     public BouncerSwipeTouchHandlerTest(FlagsParameterization flags) {
@@ -168,7 +181,13 @@
     @Before
     public void setup() {
         mKosmos = new KosmosJavaAdapter(this);
+        mContext.ensureTestableResources();
+        mResources = mContext.getResources();
+        overrideConfiguration(mConfiguration);
+        mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT;
+
         mSceneInteractor = spy(mKosmos.getSceneInteractor());
+        mKeyguardStateController = mKosmos.getKeyguardStateController();
 
         MockitoAnnotations.initMocks(this);
         mTouchHandler = new BouncerSwipeTouchHandler(
@@ -188,7 +207,9 @@
                 mKeyguardInteractor,
                 mSceneInteractor,
                 mKosmos.getShadeRepository(),
-                Optional.of(() -> mWindowRootView)
+                Optional.of(() -> mWindowRootView),
+                mKeyguardStateController,
+                mKosmos.getCommunalSettingsInteractor()
         );
 
         when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
@@ -197,6 +218,9 @@
         when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
         when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
         when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false));
+        when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true);
+        when(mWindowRootView.getResources()).thenReturn(mResources);
+        setCommunalV2ConfigEnabled(true);
     }
 
     /**
@@ -586,6 +610,43 @@
         verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
     }
 
+    @Test
+    @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    public void swipeUpAboveThresholdInLandscape_keyguardRotationNotAllowed_showsBouncer() {
+        when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
+        mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+
+        final float swipeUpPercentage = .1f;
+        // The upward velocity is ignored.
+        final float velocityY = -1;
+        swipeToPosition(swipeUpPercentage, velocityY);
+
+        // Ensure show bouncer scrimmed
+        verify(mScrimController).show(true);
+        verify(mValueAnimatorCreator, never()).create(anyFloat(), anyFloat());
+        verify(mValueAnimator, never()).start();
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    public void swipeUpBelowThreshold_inLandscapeKeyguardRotationNotAllowed_noBouncer() {
+        mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+
+        final float swipeUpPercentage = .02f;
+        // The upward velocity is ignored.
+        final float velocityY = -1;
+        swipeToPosition(swipeUpPercentage, velocityY);
+
+        // no bouncer shown scrimmed
+        verify(mScrimController, never()).show(true);
+        // on touch end, bouncer hidden
+        verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage),
+                eq(KeyguardBouncerConstants.EXPANSION_HIDDEN));
+        verify(mValueAnimator, never()).addListener(any());
+    }
+
     /**
      * Tests that swiping up with a speed above the set threshold will continue the expansion.
      */
@@ -672,4 +733,15 @@
 
         inputEventListenerCaptor.getValue().onInputEvent(upEvent);
     }
+
+    private void setCommunalV2ConfigEnabled(boolean enabled) {
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_glanceableHubEnabled,
+                enabled);
+    }
+
+    private void overrideConfiguration(Configuration configuration) {
+        mContext.getOrCreateTestableResources().overrideConfiguration(
+                configuration);
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 4d02708..5249bbe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -26,7 +26,10 @@
 import androidx.test.filters.SmallTest
 import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.biometricStatusRepository
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
@@ -50,7 +53,6 @@
 import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
@@ -67,6 +69,7 @@
     @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
     @Mock private lateinit var layoutInflater: LayoutInflater
     @Mock private lateinit var sideFpsView: View
+    @Mock private lateinit var lottieAnimationView: LottieAnimationView
     @Captor private lateinit var viewCaptor: ArgumentCaptor<View>
 
     @Before
@@ -76,7 +79,7 @@
         context.addMockSystemService(WindowManager::class.java, kosmos.windowManager)
         `when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
         `when`(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
-            .thenReturn(mock(LottieAnimationView::class.java))
+            .thenReturn(lottieAnimationView)
     }
 
     @Test
@@ -184,6 +187,20 @@
         }
     }
 
+    @Test
+    fun verifyToggleAnimation_onSideFpsIndicatorViewClickedWhileEnrolling() {
+        kosmos.testScope.runTest {
+            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING)
+            )
+            setupTestConfiguration(isInRearDisplayMode = false)
+            val clickListenerCaptor = ArgumentCaptor.forClass(View.OnClickListener::class.java)
+            verify(sideFpsView).setOnClickListener(clickListenerCaptor.capture())
+            clickListenerCaptor.value.onClick(sideFpsView)
+            verify(lottieAnimationView).toggleAnimation()
+        }
+    }
+
     private suspend fun TestScope.setupTestConfiguration(isInRearDisplayMode: Boolean) {
         kosmos.fingerprintPropertyRepository.setProperties(
             sensorId = 1,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
index 682ad0c5..754011b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -125,7 +125,9 @@
                 bluetoothTileDialogAudioSharingRepository.emitAudioSourceStateUpdate()
                 runCurrent()
 
-                assertThat(value).isNull()
+                assertThat(value).isEqualTo(Unit)
+                verify(bluetoothTileDialogLogger).logAudioSharingStateChanged(true)
+                verify(bluetoothTileDialogLogger).logAudioSourceStateUpdate()
             }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
index 587f3cc8..159c15b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
@@ -62,6 +62,7 @@
             AudioSharingRepositoryImpl(
                 kosmos.localBluetoothManager,
                 kosmos.audioSharingRepository,
+                kosmos.bluetoothTileDialogLogger,
                 kosmos.testDispatcher,
             )
     }
@@ -95,6 +96,8 @@
                 audioSharingRepository.setAudioSharingAvailable(true)
                 underTest.startAudioSharing()
                 verify(leAudioBroadcastProfile).startPrivateBroadcast()
+                verify(bluetoothTileDialogLogger)
+                    .logAudioSharingRequest(AudioSharingRequest.START_BROADCAST)
             }
         }
 
@@ -105,6 +108,8 @@
                 audioSharingRepository.setAudioSharingAvailable(false)
                 underTest.startAudioSharing()
                 verify(leAudioBroadcastProfile, never()).startPrivateBroadcast()
+                verify(bluetoothTileDialogLogger, never())
+                    .logAudioSharingRequest(AudioSharingRequest.START_BROADCAST)
             }
         }
 
@@ -117,6 +122,8 @@
                 audioSharingRepository.setAudioSharingAvailable(true)
                 underTest.stopAudioSharing()
                 verify(leAudioBroadcastProfile).stopLatestBroadcast()
+                verify(bluetoothTileDialogLogger)
+                    .logAudioSharingRequest(AudioSharingRequest.STOP_BROADCAST)
             }
         }
 
@@ -140,6 +147,7 @@
                 runCurrent()
 
                 verify(leAudioBroadcastAssistant, never()).allConnectedDevices
+                verify(bluetoothTileDialogLogger, never()).logAudioSharingRequest(any())
             }
         }
 
@@ -157,6 +165,7 @@
                 runCurrent()
 
                 verify(leAudioBroadcastAssistant, never()).allConnectedDevices
+                verify(bluetoothTileDialogLogger, never()).logAudioSharingRequest(any())
             }
         }
 
@@ -177,6 +186,7 @@
                 runCurrent()
 
                 verify(leAudioBroadcastAssistant, never()).addSource(any(), any(), anyBoolean())
+                verify(bluetoothTileDialogLogger, never()).logAudioSharingRequest(any())
             }
         }
 
@@ -198,6 +208,8 @@
                 runCurrent()
 
                 verify(leAudioBroadcastAssistant).addSource(bluetoothDevice, metadata, false)
+                verify(bluetoothTileDialogLogger)
+                    .logAudioSharingRequest(AudioSharingRequest.ADD_SOURCE)
             }
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index dd4af7b..53ddcfa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -20,6 +20,7 @@
 import android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
@@ -39,13 +40,22 @@
 import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.res.R
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.transitionState
 import com.android.systemui.testKosmos
 import com.android.systemui.util.settings.fakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -368,6 +378,76 @@
             testableResources.removeOverride(R.bool.can_use_one_handed_bouncer)
         }
 
+    @Test
+    fun bouncerExpansion_lockscreenToBouncer() =
+        kosmos.runTest {
+            val bouncerExpansion by collectLastValue(underTest.bouncerExpansion)
+
+            val progress = MutableStateFlow(0f)
+            kosmos.sceneContainerRepository.setTransitionState(transitionState)
+            transitionState.value =
+                ObservableTransitionState.Transition.showOverlay(
+                    overlay = Overlays.Bouncer,
+                    fromScene = Scenes.Lockscreen,
+                    currentOverlays = flowOf(emptySet()),
+                    progress = progress,
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+
+            assertThat(bouncerExpansion).isEqualTo(0f)
+
+            progress.value = 1f
+            assertThat(bouncerExpansion).isEqualTo(1f)
+        }
+
+    @Test
+    fun bouncerExpansion_BouncerToLockscreen() =
+        kosmos.runTest {
+            val bouncerExpansion by collectLastValue(underTest.bouncerExpansion)
+
+            val progress = MutableStateFlow(0f)
+            kosmos.sceneContainerRepository.setTransitionState(transitionState)
+            transitionState.value =
+                ObservableTransitionState.Transition.hideOverlay(
+                    overlay = Overlays.Bouncer,
+                    toScene = Scenes.Lockscreen,
+                    currentOverlays = flowOf(emptySet()),
+                    progress = progress,
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+
+            assertThat(bouncerExpansion).isEqualTo(1f)
+
+            progress.value = 1f
+            assertThat(bouncerExpansion).isEqualTo(0f)
+        }
+
+    @Test
+    fun bouncerExpansion_shadeToLockscreenUnderBouncer() =
+        kosmos.runTest {
+            val bouncerExpansion by collectLastValue(underTest.bouncerExpansion)
+
+            val progress = MutableStateFlow(0f)
+            kosmos.sceneContainerRepository.setTransitionState(transitionState)
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = Scenes.Shade,
+                    toScene = Scenes.Lockscreen,
+                    currentScene = flowOf(Scenes.Lockscreen),
+                    progress = progress,
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                    currentOverlays = setOf(Overlays.Bouncer),
+                )
+
+            assertThat(bouncerExpansion).isEqualTo(1f)
+
+            progress.value = 1f
+            assertThat(bouncerExpansion).isEqualTo(1f)
+        }
+
     companion object {
         private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN"
         private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 7051f81..f583914 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -22,6 +22,7 @@
 import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
@@ -38,8 +39,13 @@
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.collectLastValue
@@ -56,11 +62,15 @@
 import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.advanceTimeBy
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.kotlin.verify
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
 
@@ -93,10 +103,12 @@
                 communalInteractor = communalInteractor,
                 communalSettingsInteractor = communalSettingsInteractor,
                 communalSceneInteractor = communalSceneInteractor,
+                keyguardTransitionInteractor = keyguardTransitionInteractor,
                 keyguardInteractor = keyguardInteractor,
                 systemSettings = fakeSettings,
                 notificationShadeWindowController = notificationShadeWindowController,
                 bgScope = applicationCoroutineScope,
+                applicationScope = applicationCoroutineScope,
                 mainDispatcher = testDispatcher,
                 uiEventLogger = uiEventLoggerFake,
             )
@@ -111,13 +123,13 @@
                 UserHandle.USER_CURRENT,
             )
             fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+            setCommunalV2ConfigEnabled(true)
 
             underTest.start()
 
             // Make communal available so that communalInteractor.desiredScene accurately reflects
             // scene changes instead of just returning Blank.
             runBlocking { setCommunalAvailable(true) }
-            setCommunalV2ConfigEnabled(true)
         }
     }
 
@@ -414,6 +426,107 @@
             assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
         }
 
+    @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER)
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    fun glanceableHubOrientationAware_idleOnCommunal() =
+        kosmos.runTest {
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+
+            val scene by collectLastValue(communalSceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+            verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true)
+        }
+
+    @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER)
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    fun glanceableHubOrientationAware_transitioningToCommunal() =
+        kosmos.runTest {
+            val progress = MutableStateFlow(0f)
+            val targetScene = CommunalScenes.Communal
+            val currentScene = CommunalScenes.Blank
+            val transitionState =
+                MutableStateFlow(
+                    ObservableTransitionState.Transition(
+                        fromScene = currentScene,
+                        toScene = targetScene,
+                        currentScene = flowOf(targetScene),
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            communalSceneInteractor.setTransitionState(transitionState)
+
+            // Partially transition.
+            progress.value = .4f
+
+            val scene by collectLastValue(communalSceneInteractor.currentScene)
+            assertThat(scene).isEqualTo(CommunalScenes.Blank)
+
+            verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true)
+        }
+
+    @Test
+    @DisableFlags(FLAG_SCENE_CONTAINER)
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    fun glanceableHubOrientationAware_communalToDreaming() =
+        kosmos.runTest {
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+
+            verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true)
+            Mockito.clearInvocations(notificationShadeWindowController)
+
+            val progress = MutableStateFlow(0f)
+            val currentScene = CommunalScenes.Communal
+            val targetScene = CommunalScenes.Blank
+            val transitionState =
+                MutableStateFlow(
+                    ObservableTransitionState.Transition(
+                        fromScene = currentScene,
+                        toScene = targetScene,
+                        currentScene = flowOf(targetScene),
+                        progress = progress,
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            communalSceneInteractor.setTransitionState(transitionState)
+
+            // Partially transitioned out of Communal scene
+            progress.value = .4f
+
+            // Started keyguard transitioning from hub -> dreaming.
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.DREAMING,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true)
+            Mockito.clearInvocations(notificationShadeWindowController)
+
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                from = KeyguardState.GLANCEABLE_HUB,
+                to = KeyguardState.DREAMING,
+                transitionState = TransitionState.RUNNING,
+                value = 0.5f,
+            )
+
+            // Transitioned to dreaming.
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                from = KeyguardState.GLANCEABLE_HUB,
+                to = KeyguardState.DREAMING,
+                transitionState = TransitionState.FINISHED,
+                value = 1f,
+            )
+            // Not on hub anymore, let other states take control
+            verify(notificationShadeWindowController).setGlanceableHubOrientationAware(false)
+        }
+
     /**
      * Advances time by duration + 1 millisecond, to ensure that tasks scheduled to run at
      * currentTime + duration are scheduled.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
index 77d7091..dc21f06 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
@@ -134,7 +134,7 @@
             underTest.snapToScene(
                 CommunalScenes.Communal,
                 "test",
-                ActivityTransitionAnimator.TIMINGS.totalDuration
+                ActivityTransitionAnimator.TIMINGS.totalDuration,
             )
             assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
             advanceTimeBy(ActivityTransitionAnimator.TIMINGS.totalDuration)
@@ -269,6 +269,48 @@
 
     @DisableFlags(FLAG_SCENE_CONTAINER)
     @Test
+    fun isTransitioningToOrIdleOnCommunal() =
+        testScope.runTest {
+            // isIdleOnCommunal is false when not on communal.
+            val isTransitioningToOrIdleOnCommunal by
+                collectLastValue(underTest.isTransitioningToOrIdleOnCommunal)
+            assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(false)
+
+            val transitionState: MutableStateFlow<ObservableTransitionState> =
+                MutableStateFlow(
+                    ObservableTransitionState.Transition(
+                        fromScene = CommunalScenes.Blank,
+                        toScene = CommunalScenes.Communal,
+                        currentScene = flowOf(CommunalScenes.Communal),
+                        progress = flowOf(0f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+
+            // Start transition to communal.
+            repository.setTransitionState(transitionState)
+            assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(true)
+
+            // Finish transition to communal
+            transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal)
+            assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(true)
+
+            // Start transition away from communal.
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = CommunalScenes.Communal,
+                    toScene = CommunalScenes.Blank,
+                    currentScene = flowOf(CommunalScenes.Blank),
+                    progress = flowOf(.1f),
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+            assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(false)
+        }
+
+    @DisableFlags(FLAG_SCENE_CONTAINER)
+    @Test
     fun isCommunalVisible() =
         testScope.runTest {
             // isCommunalVisible is false when not on communal.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt
index 6b9e23a..135e9a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,28 +16,46 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.content.res.mainResources
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.ui.transitions.blurConfig
+import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.collectValues
+import com.android.systemui.kosmos.runCurrent
 import com.android.systemui.kosmos.runTest
+import com.android.systemui.statusbar.policy.keyguardStateController
 import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class GlanceableHubToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
+    private val kosmos =
+        testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources }
     private val underTest by lazy { kosmos.glanceableHubToPrimaryBouncerTransitionViewModel }
 
+    @Before
+    fun setUp() {
+        with(kosmos) { setCommunalV2ConfigEnabled(true) }
+    }
+
     @Test
     @DisableSceneContainer
     @DisableFlags(FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
@@ -84,4 +102,81 @@
                 },
             )
         }
+
+    @Test
+    @DisableSceneContainer
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
+    fun willDelayBouncerAppearAnimation_flagDisabled_isFalse() =
+        kosmos.runTest {
+            // keyguard rotation is not allowed on device.
+            whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false)
+
+            val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+            runCurrent()
+            // Device is idle on communal.
+            assertThat(isIdleOnCommunal).isTrue()
+
+            // in landscape
+            assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse()
+            // in portrait
+            assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse()
+        }
+
+    @Test
+    @DisableSceneContainer
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    fun willDelayBouncerAppearAnimation_keyguardRotationAllowed_isFalse() =
+        kosmos.runTest {
+            // Keyguard rotation is allowed on device.
+            whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true)
+
+            val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+            runCurrent()
+            // Device is idle on communal.
+            assertThat(isIdleOnCommunal).isTrue()
+
+            // in landscape
+            assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse()
+            // in portrait
+            assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse()
+        }
+
+    @Test
+    @DisableSceneContainer
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    fun willDelayBouncerAppearAnimation_isNotIdleOnCommunal_isFalse() =
+        kosmos.runTest {
+            whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false)
+
+            val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal)
+            communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
+            runCurrent()
+            // Device is not on communal.
+            assertThat(isIdleOnCommunal).isFalse()
+
+            // in landscape
+            assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse()
+            // in portrait
+            assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse()
+        }
+
+    @Test
+    @DisableSceneContainer
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    fun willDelayBouncerAppearAnimation_isIdleOnCommunalAndKeyguardRotationIsNotAllowed() =
+        kosmos.runTest {
+            whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false)
+            val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+            runCurrent()
+            // Device is idle on communal.
+            assertThat(isIdleOnCommunal).isTrue()
+
+            // Will delay in landscape
+            assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isTrue()
+            // Won't delay in portrait
+            assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 764068e..3407cd5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -84,12 +84,12 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
-import java.util.List;
-import java.util.concurrent.Executor;
-
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
 import platform.test.runner.parameterized.Parameters;
 
+import java.util.List;
+import java.util.concurrent.Executor;
+
 @RunWith(ParameterizedAndroidJunit4.class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -410,6 +410,19 @@
     }
 
     @Test
+    public void hubOrientationAware_layoutParamsUpdated() {
+        mNotificationShadeWindowController.setKeyguardShowing(false);
+        mNotificationShadeWindowController.setBouncerShowing(false);
+        mNotificationShadeWindowController.setGlanceableHubOrientationAware(true);
+        when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
+        mNotificationShadeWindowController.onConfigChanged(new Configuration());
+
+        verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat(mLayoutParameters.getValue().screenOrientation)
+                .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_USER);
+    }
+
+    @Test
     public void batchApplyWindowLayoutParams_doesNotDispatchEvents() {
         mNotificationShadeWindowController.setForceDozeBrightness(true);
         verify(mWindowManager).updateViewLayout(any(), any());
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index f4a43a4..ddad230 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -151,9 +151,17 @@
             create: (ClockId) -> ClockController = ::failFactory,
             getPickerConfig: (ClockSettings) -> ClockPickerConfig = ::failPickerConfig,
         ): FakeClockPlugin {
-            metadata.add(ClockMetadata(id))
-            createCallbacks[id] = create
-            pickerConfigs[id] = getPickerConfig
+            return addClock(ClockMetadata(id), create, getPickerConfig)
+        }
+
+        fun addClock(
+            metadata: ClockMetadata,
+            create: (ClockId) -> ClockController = ::failFactory,
+            getPickerConfig: (ClockSettings) -> ClockPickerConfig = ::failPickerConfig,
+        ): FakeClockPlugin {
+            this.metadata.add(metadata)
+            createCallbacks[metadata.clockId] = create
+            pickerConfigs[metadata.clockId] = getPickerConfig
             return this
         }
     }
@@ -203,28 +211,40 @@
         val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle1 = FakeLifecycle("1", plugin1)
 
-        val plugin2 = FakeClockPlugin().addClock("clock_3").addClock("clock_4")
+        val plugin2 =
+            FakeClockPlugin()
+                .addClock(ClockMetadata("clock_3", isDeprecated = false))
+                .addClock(ClockMetadata("clock_4", isDeprecated = true))
         val lifecycle2 = FakeLifecycle("2", plugin2)
 
         pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
         pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
-        val list = registry.getClocks()
         assertEquals(
-            list.toSet(),
             setOf(
                 ClockMetadata(DEFAULT_CLOCK_ID),
                 ClockMetadata("clock_1"),
                 ClockMetadata("clock_2"),
                 ClockMetadata("clock_3"),
-                ClockMetadata("clock_4"),
             ),
+            registry.getClocks().toSet(),
+        )
+
+        assertEquals(
+            setOf(
+                ClockMetadata(DEFAULT_CLOCK_ID),
+                ClockMetadata("clock_1"),
+                ClockMetadata("clock_2"),
+                ClockMetadata("clock_3"),
+                ClockMetadata("clock_4", isDeprecated = true),
+            ),
+            registry.getClocks(includeDeprecated = true).toSet(),
         )
     }
 
     @Test
     fun noPlugins_createDefaultClock() {
         val clock = registry.createCurrentClock()
-        assertEquals(clock, mockDefaultClock)
+        assertEquals(mockDefaultClock, clock)
     }
 
     @Test
@@ -242,18 +262,18 @@
         pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
         val list = registry.getClocks()
         assertEquals(
-            list.toSet(),
             setOf(
                 ClockMetadata(DEFAULT_CLOCK_ID),
                 ClockMetadata("clock_1"),
                 ClockMetadata("clock_2"),
             ),
+            list.toSet(),
         )
 
-        assertEquals(registry.createExampleClock("clock_1"), mockClock)
-        assertEquals(registry.createExampleClock("clock_2"), mockClock)
-        assertEquals(registry.getClockPickerConfig("clock_1"), pickerConfig)
-        assertEquals(registry.getClockPickerConfig("clock_2"), pickerConfig)
+        assertEquals(mockClock, registry.createExampleClock("clock_1"))
+        assertEquals(mockClock, registry.createExampleClock("clock_2"))
+        assertEquals(pickerConfig, registry.getClockPickerConfig("clock_1"))
+        assertEquals(pickerConfig, registry.getClockPickerConfig("clock_2"))
         verify(lifecycle1, never()).unloadPlugin()
         verify(lifecycle2, times(2)).unloadPlugin()
     }
@@ -305,7 +325,7 @@
         pluginListener.onPluginUnloaded(plugin2, lifecycle2)
 
         val clock = registry.createCurrentClock()
-        assertEquals(clock, mockDefaultClock)
+        assertEquals(mockDefaultClock, clock)
     }
 
     @Test
@@ -482,13 +502,13 @@
 
         // Verify all plugins were correctly loaded into the registry
         assertEquals(
-            registry.getClocks().toSet(),
             setOf(
                 ClockMetadata("DEFAULT"),
                 ClockMetadata("clock_2"),
                 ClockMetadata("clock_3"),
                 ClockMetadata("clock_4"),
             ),
+            registry.getClocks().toSet(),
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 9eba410..4f30103 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -108,7 +108,7 @@
         verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA)
         verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA)
 
-        clock.initialize(true, 0f, 0f)
+        clock.initialize(true, 0f, 0f, {})
 
         val expectedColor = 0
         verify(mockSmallClockView).setColors(DOZE_COLOR, expectedColor)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
index 32fec32..4c1f645 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
@@ -13,6 +13,7 @@
  */
 package com.android.systemui.plugins.clocks
 
+import android.graphics.RectF
 import com.android.systemui.plugins.annotations.ProtectedInterface
 import com.android.systemui.plugins.annotations.SimpleProperty
 import java.io.PrintWriter
@@ -37,7 +38,12 @@
     val events: ClockEvents
 
     /** Initializes various rendering parameters. If never called, provides reasonable defaults. */
-    fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float)
+    fun initialize(
+        isDarkTheme: Boolean,
+        dozeFraction: Float,
+        foldFraction: Float,
+        onBoundsChanged: (RectF) -> Unit,
+    )
 
     /** Optional method for dumping debug information */
     fun dump(pw: PrintWriter)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 7426f06..0ef62a3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -53,5 +53,21 @@
 /** Identifies a clock design */
 typealias ClockId = String
 
-/** Some data about a clock design */
-data class ClockMetadata(val clockId: ClockId)
+/** Some metadata about a clock design */
+data class ClockMetadata(
+    /** Id for the clock design. */
+    val clockId: ClockId,
+
+    /**
+     * true if this clock is deprecated and should not be used. The ID may still show up in certain
+     * locations to help migrations, but it will not be selectable by new users.
+     */
+    val isDeprecated: Boolean = false,
+
+    /**
+     * Optional mapping of a legacy clock to a new id. This will map users that already are using
+     * `clockId` to the `replacementTarget` instead. The provider should still support the old id
+     * w/o crashing, but can consider it deprecated and the id reserved.
+     */
+    val replacementTarget: ClockId? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Default.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Default.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/DisplayId.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/DisplayId.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/InstrumentationTest.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/InstrumentationTest.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/InstrumentationTest.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/InstrumentationTest.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/LongRunning.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/LongRunning.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/LongRunning.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/LongRunning.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/NotifInflation.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/NotifInflation.kt
index 231fb2d..52a808d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/NotifInflation.kt
@@ -21,4 +21,4 @@
 @Qualifier
 @MustBeDocumented
 @Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+public annotation class NotifInflation
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/PerUser.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/PerUser.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/RootView.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/RootView.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/RootView.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/RootView.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/SystemUser.kt
similarity index 88%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
copy to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/SystemUser.kt
index 231fb2d..0ea815f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/SystemUser.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.systemui.dagger.qualifiers
 
 import javax.inject.Qualifier
@@ -21,4 +20,4 @@
 @Qualifier
 @MustBeDocumented
 @Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+public annotation class SystemUser
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/TestHarness.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/TestHarness.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/TestHarness.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/TestHarness.java
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 6d44645..24fd860 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -115,6 +115,7 @@
     <dimen name="below_clock_padding_end">16dp</dimen>
     <dimen name="below_clock_padding_start_icons">28dp</dimen>
     <dimen name="smartspace_padding_horizontal">16dp</dimen>
+    <dimen name="smartspace_padding_vertical">12dp</dimen>
 
     <!-- Proportion of the screen height to use to set the maximum height of the bouncer to when
          the device is in the DEVICE_POSTURE_HALF_OPENED posture, for the PIN/pattern entry. 0 will
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 1b8282b..9434234 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -21,6 +21,7 @@
 import android.content.Intent
 import android.content.IntentFilter
 import android.content.res.Resources
+import android.graphics.RectF
 import android.os.Trace
 import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
 import android.provider.Settings.Global.ZEN_MODE_OFF
@@ -78,7 +79,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
@@ -147,7 +148,7 @@
         val clockStr = clock.toString()
         loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } }
 
-        clock.initialize(isDarkTheme(), dozeAmount, 0f)
+        clock.initialize(isDarkTheme(), dozeAmount.value, 0f, { onClockBoundsChanged.value = it })
 
         if (!regionSamplingEnabled) {
             updateColors()
@@ -240,17 +241,16 @@
     private var smallClockFrame: ViewGroup? = null
     private var onGlobalLayoutListener: OnGlobalLayoutListener? = null
 
-    private var isDozing = false
-        private set
-
     private var isCharging = false
-    private var dozeAmount = 0f
     private var isKeyguardVisible = false
     private var isRegistered = false
     private var disposableHandle: DisposableHandle? = null
     private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
     private var largeClockOnSecondaryDisplay = false
 
+    val dozeAmount = MutableStateFlow(0f)
+    val onClockBoundsChanged = MutableStateFlow<RectF?>(null)
+
     private fun isDarkTheme(): Boolean {
         val isLightTheme = TypedValue()
         context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
@@ -306,7 +306,7 @@
     var smallTimeListener: TimeListener? = null
     var largeTimeListener: TimeListener? = null
     val shouldTimeListenerRun: Boolean
-        get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD
+        get() = isKeyguardVisible && dozeAmount.value < DOZE_TICKRATE_THRESHOLD
 
     private var weatherData: WeatherData? = null
     private var zenData: ZenData? = null
@@ -466,7 +466,6 @@
         disposableHandle =
             parent.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    listenForDozing(this)
                     if (ModesUi.isEnabled) {
                         listenForDnd(this)
                     }
@@ -576,17 +575,17 @@
     }
 
     private fun handleDoze(doze: Float) {
-        dozeAmount = doze
         clock?.run {
             Trace.beginSection("$TAG#smallClock.animations.doze")
-            smallClock.animations.doze(dozeAmount)
+            smallClock.animations.doze(doze)
             Trace.endSection()
             Trace.beginSection("$TAG#largeClock.animations.doze")
-            largeClock.animations.doze(dozeAmount)
+            largeClock.animations.doze(doze)
             Trace.endSection()
         }
         smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
         largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
+        dozeAmount.value = doze
     }
 
     @VisibleForTesting
@@ -642,18 +641,6 @@
         }
     }
 
-    @VisibleForTesting
-    internal fun listenForDozing(scope: CoroutineScope): Job {
-        return scope.launch {
-            combine(keyguardInteractor.dozeAmount, keyguardInteractor.isDozing) {
-                    localDozeAmount,
-                    localIsDozing ->
-                    localDozeAmount > dozeAmount || localIsDozing
-                }
-                .collect { localIsDozing -> isDozing = localIsDozing }
-        }
-    }
-
     class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
         val predrawListener =
             ViewTreeObserver.OnPreDrawListener {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 73dc282..e2f3955 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -172,6 +172,7 @@
     private boolean mIsDragging;
     private float mStartTouchY = -1;
     private boolean mDisappearAnimRunning;
+    private boolean mIsAppearAnimationDelayed;
     private SwipeListener mSwipeListener;
     private ViewMode mViewMode = new DefaultViewMode();
     private boolean mIsInteractable;
@@ -583,6 +584,10 @@
         return false;
     }
 
+    boolean isAppearAnimationDelayed() {
+        return mIsAppearAnimationDelayed;
+    }
+
     void addMotionEventListener(Gefingerpoken listener) {
         mMotionEventListeners.add(listener);
     }
@@ -624,6 +629,19 @@
         mViewMode.startAppearAnimation(securityMode);
     }
 
+    /**
+     * Set view translationY and alpha as we delay bouncer animation.
+     */
+    public void setupForDelayedAppear() {
+        setTranslationY(0f);
+        setAlpha(0f);
+        setIsAppearAnimationDelayed(true);
+    }
+
+    public void setIsAppearAnimationDelayed(boolean isDelayed) {
+        mIsAppearAnimationDelayed = isDelayed;
+    }
+
     private void beginJankInstrument(int cuj) {
         KeyguardInputView securityView = mSecurityViewFlipper.getSecurityView();
         if (securityView == null) return;
@@ -812,6 +830,7 @@
     public void reset() {
         mViewMode.reset();
         mDisappearAnimRunning = false;
+        mIsAppearAnimationDelayed = false;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index d10fce4..198c1cb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -18,6 +18,7 @@
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
@@ -385,6 +386,10 @@
                         boolean useSplitBouncer = orientation == ORIENTATION_LANDSCAPE;
                         mSecurityViewFlipperController.updateConstraints(useSplitBouncer);
                     }
+                    if (orientation == ORIENTATION_PORTRAIT) {
+                        // If there is any delayed bouncer appear animation it can start now
+                        startAppearAnimationIfDelayed();
+                    }
                 }
 
                 @Override
@@ -845,6 +850,16 @@
         }
     }
 
+    /** Start appear animation which was previously delayed from opening bouncer in landscape. */
+    public void startAppearAnimationIfDelayed() {
+        if (!mView.isAppearAnimationDelayed()) {
+            return;
+        }
+        setAlpha(1f);
+        appear();
+        mView.setIsAppearAnimationDelayed(false);
+    }
+
     /** Called when the bouncer changes visibility. */
     public void onBouncerVisibilityChanged(boolean isVisible) {
         if (!isVisible) {
@@ -1301,4 +1316,13 @@
         setAlpha(MathUtils.constrain(1 - scaledFraction, 0f, 1f));
         mView.setTranslationY(scaledFraction * mTranslationY);
     }
+
+    /** Set up view for delayed appear animation. */
+    public void setupForDelayedAppear() {
+        mView.setupForDelayedAppear();
+    }
+
+    public boolean isLandscapeOrientation() {
+        return mLastOrientation == Configuration.ORIENTATION_LANDSCAPE;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
index d8e7a16..97de78c 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -18,6 +18,7 @@
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
+import android.content.res.Configuration
 import android.graphics.Rect
 import android.graphics.Region
 import android.util.Log
@@ -36,6 +37,7 @@
 import com.android.systemui.ambient.touch.scrim.ScrimController
 import com.android.systemui.ambient.touch.scrim.ScrimManager
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
@@ -46,6 +48,7 @@
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.wm.shell.animation.FlingAnimationUtils
 import java.util.Optional
 import javax.inject.Inject
@@ -82,6 +85,8 @@
     private val sceneInteractor: SceneInteractor,
     private val shadeRepository: ShadeRepository,
     private val windowRootViewProvider: Optional<Provider<WindowRootView>>,
+    private val keyguardStateController: KeyguardStateController,
+    communalSettingsInteractor: CommunalSettingsInteractor,
 ) : TouchHandler {
     /** An interface for creating ValueAnimators. */
     interface ValueAnimatorCreator {
@@ -101,6 +106,8 @@
     private var capture: Boolean? = null
     private var expanded: Boolean = false
     private var touchSession: TouchSession? = null
+    private var isUserTrackingExpansionDisabled: Boolean = false
+    private var isKeyguardScreenRotationAllowed: Boolean = false
     private val scrimManagerCallback =
         ScrimManager.Callback { controller ->
             currentScrimController?.reset()
@@ -121,6 +128,9 @@
                 distanceX: Float,
                 distanceY: Float,
             ): Boolean {
+                val isLandscape =
+                    windowRootView.resources.configuration.orientation ==
+                        Configuration.ORIENTATION_LANDSCAPE
                 if (capture == null) {
                     capture =
                         if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
@@ -137,7 +147,9 @@
                         // reset expanding
                         expanded = false
                         // Since the user is dragging the bouncer up, set scrimmed to false.
-                        currentScrimController?.show()
+                        if (isKeyguardScreenRotationAllowed || !isLandscape) {
+                            currentScrimController?.show(false)
+                        }
 
                         if (SceneContainerFlag.isEnabled) {
                             sceneInteractor.onRemoteUserInputStarted("bouncer touch handler")
@@ -172,6 +184,37 @@
                         return true
                     }
 
+                    if (touchSession == null) {
+                        return true
+                    }
+                    val screenTravelPercentage =
+                        (abs((y - e2.y).toDouble()) / touchSession!!.bounds.height()).toFloat()
+
+                    if (communalSettingsInteractor.isV2FlagEnabled()) {
+                        if (isUserTrackingExpansionDisabled) return true
+                        // scrolling up in landscape orientation but device doesn't allow keyguard
+                        // screen rotation
+                        if (y > e2.y && !isKeyguardScreenRotationAllowed && isLandscape) {
+                            velocityTracker!!.computeCurrentVelocity(1000)
+                            currentExpansion = 1 - screenTravelPercentage
+                            expanded =
+                                shouldExpandBouncer(
+                                    velocityTracker!!.yVelocity,
+                                    velocityTracker!!.xVelocity,
+                                    EXPANSION_FROM_LANDSCAPE_THRESHOLD,
+                                    currentExpansion,
+                                )
+                            if (expanded) {
+                                // Once scroll past the percentage threshold, show bouncer scrimmed,
+                                // so that user won't be required to drag up and then right to keep
+                                // bouncer open after screen rotates to portrait.
+                                currentScrimController?.show(true)
+                                isUserTrackingExpansionDisabled = true
+                            }
+                            return true
+                        }
+                    }
+
                     if (SceneContainerFlag.isEnabled) {
                         windowRootView.dispatchTouchEvent(e2)
                     } else {
@@ -182,12 +225,7 @@
                         // is fully hidden at full expansion (1) and fully visible when fully
                         // collapsed
                         // (0).
-                        touchSession?.apply {
-                            val screenTravelPercentage =
-                                (abs((this@outer.y - e2.y).toDouble()) / getBounds().height())
-                                    .toFloat()
-                            setPanelExpansion(1 - screenTravelPercentage)
-                        }
+                        touchSession?.apply { setPanelExpansion(1 - screenTravelPercentage) }
                     }
                 }
 
@@ -262,6 +300,7 @@
         }
         scrimManager.addCallback(scrimManagerCallback)
         currentScrimController = scrimManager.currentController
+        isKeyguardScreenRotationAllowed = keyguardStateController.isKeyguardScreenRotationAllowed()
 
         shadeRepository.setLegacyShadeTracking(true)
         session.registerCallback {
@@ -271,6 +310,7 @@
             scrimManager.removeCallback(scrimManagerCallback)
             capture = null
             touchSession = null
+            isUserTrackingExpansionDisabled = false
             if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
                 notificationShadeWindowController.setForcePluginOpen(false, this)
             }
@@ -299,14 +339,25 @@
                     return
                 }
 
+                // We are already in progress of opening bouncer scrimmed
+                if (isUserTrackingExpansionDisabled) {
+                    // User is done scrolling, reset
+                    isUserTrackingExpansionDisabled = false
+                    return
+                }
+
                 // We must capture the resulting velocities as resetMonitor() will clear these
                 // values.
                 velocityTracker!!.computeCurrentVelocity(1000)
                 val verticalVelocity = velocityTracker!!.yVelocity
-                val horizontalVelocity = velocityTracker!!.xVelocity
-                val velocityVector =
-                    hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat()
-                expanded = !flingRevealsOverlay(verticalVelocity, velocityVector)
+                expanded =
+                    shouldExpandBouncer(
+                        verticalVelocity,
+                        velocityTracker!!.xVelocity,
+                        FLING_PERCENTAGE_THRESHOLD,
+                        currentExpansion,
+                    )
+
                 val expansion =
                     if (expanded!!) KeyguardBouncerConstants.EXPANSION_VISIBLE
                     else KeyguardBouncerConstants.EXPANSION_HIDDEN
@@ -339,11 +390,27 @@
         return animator
     }
 
-    protected fun flingRevealsOverlay(velocity: Float, velocityVector: Float): Boolean {
+    private fun shouldExpandBouncer(
+        verticalVelocity: Float,
+        horizontalVelocity: Float,
+        threshold: Float,
+        expansion: Float,
+    ): Boolean {
+        val velocityVector =
+            hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat()
+        return !flingRevealsOverlay(verticalVelocity, velocityVector, threshold, expansion)
+    }
+
+    protected fun flingRevealsOverlay(
+        velocity: Float,
+        velocityVector: Float,
+        threshold: Float,
+        expansion: Float,
+    ): Boolean {
         // Fully expand the space above the bouncer, if the user has expanded the bouncer less
         // than halfway or final velocity was positive, indicating a downward direction.
         return if (abs(velocityVector.toDouble()) < flingAnimationUtils.minVelocityPxPerSecond) {
-            currentExpansion > FLING_PERCENTAGE_THRESHOLD
+            expansion > threshold
         } else {
             velocity > 0
         }
@@ -390,6 +457,7 @@
 
     companion object {
         const val FLING_PERCENTAGE_THRESHOLD = 0.5f
+        const val EXPANSION_FROM_LANDSCAPE_THRESHOLD = 0.95f
         private const val TAG = "BouncerSwipeTouchHandler"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
index 94c9982..6f2dd79 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
@@ -33,8 +33,8 @@
     }
 
     @Override
-    public void show() {
-        mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
+    public void show(boolean scrimmed) {
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(scrimmed);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
index 0054352..90cbd25 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
@@ -25,8 +25,9 @@
 public interface ScrimController {
     /**
      * Called at the start of expansion before any expansion amount updates.
+     * @param scrimmed true when the bouncer should show scrimmed, false when user will be dragging.
      */
-    default void show() {
+    default void show(boolean scrimmed) {
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index bbf9a1901..30b98a6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -137,6 +137,9 @@
             )
         bind(overlayView!!, overlayViewModel, windowManager.get())
         overlayView!!.visibility = View.INVISIBLE
+        overlayView!!.setOnClickListener { v ->
+            v.requireViewById<LottieAnimationView>(R.id.sidefps_animation).toggleAnimation()
+        }
         Log.d(TAG, "show(): adding overlayView $overlayView")
         windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
     }
@@ -234,3 +237,11 @@
         resumeAnimation()
     }
 }
+
+fun LottieAnimationView.toggleAnimation() {
+    if (isAnimating) {
+        pauseAnimation()
+    } else {
+        resumeAnimation()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
index 94fca21..f8f692d 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
@@ -124,10 +124,18 @@
             when (deviceItem.type) {
                 DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
                     uiEventLogger.log(BluetoothTileDialogUiEvent.CHECK_MARK_ACTION_BUTTON_CLICKED)
+                    logger.logAudioSharingButtonClick(
+                        AudioSharingButtonClick.CHECK_MARK,
+                        deviceItem,
+                    )
                     audioSharingInteractor.stopAudioSharing()
                 }
                 DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
                     uiEventLogger.log(BluetoothTileDialogUiEvent.PLUS_ACTION_BUTTON_CLICKED)
+                    logger.logAudioSharingButtonClick(
+                        AudioSharingButtonClick.PLUS_BUTTON,
+                        deviceItem,
+                    )
                     audioSharingInteractor.startAudioSharing()
                 }
                 else -> {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
index 832afb1..7a76eed 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -40,6 +40,7 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.receiveAsFlow
 import kotlinx.coroutines.withContext
 
@@ -73,6 +74,7 @@
     private val context: Context,
     private val localBluetoothManager: LocalBluetoothManager?,
     private val audioSharingRepository: AudioSharingRepository,
+    private val logger: BluetoothTileDialogLogger,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : AudioSharingInteractor {
 
@@ -92,9 +94,12 @@
 
     override val audioSourceStateUpdate =
         isAudioSharingOn
+            .onEach { logger.logAudioSharingStateChanged(it) }
             .flatMapLatest {
                 if (it) {
-                    audioSharingRepository.audioSourceStateUpdate
+                    audioSharingRepository.audioSourceStateUpdate.onEach {
+                        logger.logAudioSourceStateUpdate()
+                    }
                 } else {
                     emptyFlow()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
index 44f9769..d84b34a 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
@@ -53,6 +53,7 @@
 class AudioSharingRepositoryImpl(
     private val localBluetoothManager: LocalBluetoothManager,
     private val settingsLibAudioSharingRepository: SettingsLibAudioSharingRepository,
+    private val logger: BluetoothTileDialogLogger,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : AudioSharingRepository {
 
@@ -79,7 +80,11 @@
             }
             leAudioBroadcastProfile?.latestBluetoothLeBroadcastMetadata?.let { metadata ->
                 leAudioBroadcastAssistantProfile?.let {
-                    it.allConnectedDevices.forEach { sink -> it.addSource(sink, metadata, false) }
+                    it.allConnectedDevices.forEach { sink ->
+                        it.addSource(sink, metadata, false).also {
+                            logger.logAudioSharingRequest(AudioSharingRequest.ADD_SOURCE)
+                        }
+                    }
                 }
             }
         }
@@ -99,7 +104,9 @@
             if (!settingsLibAudioSharingRepository.audioSharingAvailable()) {
                 return@withContext
             }
-            leAudioBroadcastProfile?.startPrivateBroadcast()
+            leAudioBroadcastProfile?.startPrivateBroadcast().also {
+                logger.logAudioSharingRequest(AudioSharingRequest.START_BROADCAST)
+            }
         }
     }
 
@@ -108,7 +115,9 @@
             if (!settingsLibAudioSharingRepository.audioSharingAvailable()) {
                 return@withContext
             }
-            leAudioBroadcastProfile?.stopLatestBroadcast()
+            leAudioBroadcastProfile?.stopLatestBroadcast().also {
+                logger.logAudioSharingRequest(AudioSharingRequest.STOP_BROADCAST)
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
index 576acd2..5a5a51e 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
@@ -218,7 +218,7 @@
                     scrollViewContent.layoutParams.height = WRAP_CONTENT
                     lastUiUpdateMs = systemClock.elapsedRealtime()
                     lastItemRow = itemRow
-                    logger.logDeviceUiUpdate(lastUiUpdateMs - start)
+                    logger.logDeviceUiUpdate(lastUiUpdateMs - start, deviceItem)
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
index 06116f0..5f866c5 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
@@ -27,18 +27,29 @@
 enum class BluetoothStateStage {
     USER_TOGGLED,
     BLUETOOTH_STATE_VALUE_SET,
-    BLUETOOTH_STATE_CHANGE_RECEIVED
+    BLUETOOTH_STATE_CHANGE_RECEIVED,
 }
 
 enum class DeviceFetchTrigger {
     FIRST_LOAD,
     BLUETOOTH_STATE_CHANGE_RECEIVED,
-    BLUETOOTH_CALLBACK_RECEIVED
+    BLUETOOTH_CALLBACK_RECEIVED,
+}
+
+enum class AudioSharingButtonClick {
+    PLUS_BUTTON,
+    CHECK_MARK,
+}
+
+enum class AudioSharingRequest {
+    START_BROADCAST,
+    STOP_BROADCAST,
+    ADD_SOURCE,
 }
 
 enum class JobStatus {
     FINISHED,
-    CANCELLED
+    CANCELLED,
 }
 
 class BluetoothTileDialogLogger
@@ -53,7 +64,7 @@
                 str1 = stage.toString()
                 str2 = state
             },
-            { "BluetoothState. stage=$str1 state=$str2" }
+            { "BluetoothState. stage=$str1 state=$str2" },
         )
 
     fun logDeviceClick(address: String, type: DeviceItemType) =
@@ -64,7 +75,7 @@
                 str1 = address
                 str2 = type.toString()
             },
-            { "DeviceClick. address=$str1 type=$str2" }
+            { "DeviceClick. address=$str1 type=$str2" },
         )
 
     fun logActiveDeviceChanged(address: String?, profileId: Int) =
@@ -75,7 +86,7 @@
                 str1 = address
                 int1 = profileId
             },
-            { "ActiveDeviceChanged. address=$str1 profileId=$int1" }
+            { "ActiveDeviceChanged. address=$str1 profileId=$int1" },
         )
 
     fun logProfileConnectionStateChanged(address: String, state: String, profileId: Int) =
@@ -87,7 +98,7 @@
                 str2 = state
                 int1 = profileId
             },
-            { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" }
+            { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" },
         )
 
     fun logBatteryChanged(address: String, key: Int, value: ByteArray?) =
@@ -99,7 +110,7 @@
                 int1 = key
                 str2 = value?.toString() ?: ""
             },
-            { "BatteryChanged. address=$str1 key=$int1 value=$str2" }
+            { "BatteryChanged. address=$str1 key=$int1 value=$str2" },
         )
 
     fun logDeviceFetch(status: JobStatus, trigger: DeviceFetchTrigger, duration: Long) =
@@ -111,18 +122,26 @@
                 str2 = trigger.toString()
                 long1 = duration
             },
-            { "DeviceFetch. status=$str1 trigger=$str2 duration=$long1" }
+            { "DeviceFetch. status=$str1 trigger=$str2 duration=$long1" },
         )
 
-    fun logDeviceUiUpdate(duration: Long) =
-        logBuffer.log(TAG, DEBUG, { long1 = duration }, { "DeviceUiUpdate. duration=$long1" })
+    fun logDeviceUiUpdate(duration: Long, deviceItem: List<DeviceItem>) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                long1 = duration
+                str1 = deviceItem.toString()
+            },
+            { "DeviceUiUpdate. duration=$long1 deviceItem=$str1" },
+        )
 
     fun logDeviceClickInAudioSharingWhenEnabled(inAudioSharing: Boolean) {
         logBuffer.log(
             TAG,
             DEBUG,
             { str1 = inAudioSharing.toString() },
-            { "DeviceClick. in audio sharing=$str1" }
+            { "DeviceClick. in audio sharing=$str1" },
         )
     }
 
@@ -138,7 +157,36 @@
                 str1 = criteria
                 str2 = deviceItem.toString()
             },
-            { "$str1. deviceItem=$str2" }
+            { "$str1. deviceItem=$str2" },
         )
     }
+
+    fun logAudioSharingStateChanged(stateOn: Boolean) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = stateOn.toString() },
+            { "AudioSharingStateChanged. state=$str1" },
+        )
+
+    fun logAudioSourceStateUpdate() = logBuffer.log(TAG, DEBUG, {}, { "AudioSourceStateUpdate" })
+
+    fun logAudioSharingButtonClick(click: AudioSharingButtonClick, deviceItem: DeviceItem) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = click.toString()
+                str2 = deviceItem.toString()
+            },
+            { "AudioSharingButtonClick. click=$str1 deviceItem=$str2" },
+        )
+
+    fun logAudioSharingRequest(apiCall: AudioSharingRequest) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = apiCall.toString() },
+            { "AudioSharingRequest. apiCall=$str1" },
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
index afe9a1e..01c4b21 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.bluetooth.qsdialog.AudioSharingRepositoryImpl
 import com.android.systemui.bluetooth.qsdialog.AvailableAudioSharingMediaDeviceItemFactory
 import com.android.systemui.bluetooth.qsdialog.AvailableMediaDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogLogger
 import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory
 import com.android.systemui.bluetooth.qsdialog.DeviceItemActionInteractor
 import com.android.systemui.bluetooth.qsdialog.DeviceItemActionInteractorImpl
@@ -53,6 +54,7 @@
         fun provideAudioSharingRepository(
             localBluetoothManager: LocalBluetoothManager?,
             settingsLibAudioSharingRepository: SettingsLibAudioSharingRepository,
+            logger: BluetoothTileDialogLogger,
             @Background backgroundDispatcher: CoroutineDispatcher,
         ): AudioSharingRepository =
             if (
@@ -62,6 +64,7 @@
                 AudioSharingRepositoryImpl(
                     localBluetoothManager,
                     settingsLibAudioSharingRepository,
+                    logger,
                     backgroundDispatcher,
                 )
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 75503e8..b26a2c0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -17,7 +17,9 @@
 package com.android.systemui.bouncer.domain.interactor
 
 import android.app.StatusBarManager.SESSION_KEYGUARD
+import com.android.app.tracing.FlowTracing.traceAsCounter
 import com.android.app.tracing.coroutines.asyncTraced as async
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
@@ -38,9 +40,12 @@
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneBackInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -49,7 +54,9 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
 /** Encapsulates business logic and application state accessing use-cases. */
@@ -65,6 +72,7 @@
     private val powerInteractor: PowerInteractor,
     private val uiEventLogger: UiEventLogger,
     private val sessionTracker: SessionTracker,
+    sceneInteractor: SceneInteractor,
     sceneBackInteractor: SceneBackInteractor,
     @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
 ) {
@@ -149,6 +157,31 @@
     val dismissDestination: Flow<SceneKey> =
         sceneBackInteractor.backScene.map { it ?: Scenes.Lockscreen }
 
+    /** The amount [0-1] that the Bouncer Overlay has been transitioned to. */
+    val bouncerExpansion: Flow<Float> =
+        if (SceneContainerFlag.isEnabled) {
+                sceneInteractor.transitionState.flatMapLatestConflated { state ->
+                    when (state) {
+                        is ObservableTransitionState.Idle ->
+                            flowOf(if (Overlays.Bouncer in state.currentOverlays) 1f else 0f)
+                        is ObservableTransitionState.Transition ->
+                            if (state.toContent == Overlays.Bouncer) {
+                                state.progress
+                            } else if (state.fromContent == Overlays.Bouncer) {
+                                state.progress.map { progress -> 1 - progress }
+                            } else {
+                                state.currentOverlays().map {
+                                    if (Overlays.Bouncer in it) 1f else 0f
+                                }
+                            }
+                    }
+                }
+            } else {
+                flowOf()
+            }
+            .distinctUntilChanged()
+            .traceAsCounter("bouncer_expansion") { (it * 100f).toInt() }
+
     /** Notifies that the user has places down a pointer, not necessarily dragging just yet. */
     fun onDown() {
         falsingInteractor.avoidGesture()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
index 7f26831..5d64219 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -15,6 +15,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
 import com.android.systemui.log.BouncerLogger
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -30,6 +31,8 @@
 constructor(
     val viewModel: KeyguardBouncerViewModel,
     val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
+    val glanceableHubToPrimaryBouncerTransitionViewModel:
+        GlanceableHubToPrimaryBouncerTransitionViewModel,
     val componentFactory: KeyguardBouncerComponent.Factory,
     val messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
     val bouncerMessageInteractor: BouncerMessageInteractor,
@@ -82,6 +85,7 @@
                 view,
                 deps.viewModel,
                 deps.primaryBouncerToGoneTransitionViewModel,
+                deps.glanceableHubToPrimaryBouncerTransitionViewModel,
                 deps.componentFactory,
                 deps.messageAreaControllerFactory,
                 deps.bouncerMessageInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index 7d8945a..45f0e13 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
 import com.android.systemui.bouncer.ui.BouncerViewDelegate
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.BouncerLogger
@@ -49,6 +50,8 @@
         view: ViewGroup,
         viewModel: KeyguardBouncerViewModel,
         primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
+        glanceableHubToPrimaryBouncerTransitionViewModel:
+            GlanceableHubToPrimaryBouncerTransitionViewModel,
         componentFactory: KeyguardBouncerComponent.Factory,
         messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
         bouncerMessageInteractor: BouncerMessageInteractor,
@@ -133,7 +136,20 @@
                                         /* turningOff= */ false
                                     )
                                     securityContainerController.setInitialMessage()
-                                    securityContainerController.appear()
+                                    // Delay bouncer appearing animation when opening it from the
+                                    // glanceable hub in landscape, until after orientation changes
+                                    // to portrait. This prevents bouncer from showing in landscape
+                                    // layout, if bouncer rotation is not allowed.
+                                    if (
+                                        glanceableHubToPrimaryBouncerTransitionViewModel
+                                            .willDelayAppearAnimation(
+                                                securityContainerController.isLandscapeOrientation
+                                            )
+                                    ) {
+                                        securityContainerController.setupForDelayedAppear()
+                                    } else {
+                                        securityContainerController.appear()
+                                    }
                                     securityContainerController.onResume(
                                         KeyguardSecurityView.SCREEN_ON
                                     )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index e36e855..49b0bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -29,10 +29,17 @@
 import com.android.systemui.communal.shared.model.CommunalScenes.isCommunal
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
 import com.android.systemui.util.kotlin.emitOnStart
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -45,6 +52,7 @@
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.withContext
@@ -60,10 +68,12 @@
     private val communalInteractor: CommunalInteractor,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
     private val communalSceneInteractor: CommunalSceneInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val systemSettings: SystemSettings,
     private val notificationShadeWindowController: NotificationShadeWindowController,
     @Background private val bgScope: CoroutineScope,
+    @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     private val uiEventLogger: UiEventLogger,
 ) : CoreStartable {
@@ -154,6 +164,25 @@
                     }
             }
         }
+
+        if (communalSettingsInteractor.isV2FlagEnabled()) {
+            applicationScope.launch(context = mainDispatcher) {
+                anyOf(
+                        communalSceneInteractor.isTransitioningToOrIdleOnCommunal,
+                        // when transitioning from hub to dream, allow hub to stay at the current
+                        // orientation, as keyguard doesn't allow rotation by default.
+                        keyguardTransitionInteractor.isInTransition(
+                            edge = Edge.create(from = Scenes.Communal, to = DREAMING),
+                            edgeWithoutSceneContainer =
+                                Edge.create(from = GLANCEABLE_HUB, to = DREAMING),
+                        ),
+                    )
+                    .distinctUntilChanged()
+                    .collectLatest {
+                        notificationShadeWindowController.setGlanceableHubOrientationAware(it)
+                    }
+            }
+        }
     }
 
     private fun cancelHubTimeout() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index 3d9e930..fed99d7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -307,6 +307,21 @@
                 initialValue = false,
             )
 
+    /** Flow that emits a boolean if transitioning to or idle on communal scene. */
+    val isTransitioningToOrIdleOnCommunal: Flow<Boolean> =
+        transitionState
+            .map {
+                (it is ObservableTransitionState.Idle &&
+                    it.currentScene == CommunalScenes.Communal) ||
+                    (it is ObservableTransitionState.Transition &&
+                        it.toContent == CommunalScenes.Communal)
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
     private companion object {
         const val TAG = "CommunalSceneInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java
deleted file mode 100644
index e69de29..0000000
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java
+++ /dev/null
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/SystemUser.kt b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/SystemUser.kt
deleted file mode 100644
index 6878a52..0000000
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/SystemUser.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.dagger.qualifiers
-
-import javax.inject.Qualifier
-
-@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class SystemUser
diff --git a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
index 96ef03c..e06c228 100644
--- a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
+++ b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.systemui.grid.ui.compose
 
+import androidx.collection.IntIntPair
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
@@ -24,7 +24,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.semantics.CollectionInfo
 import androidx.compose.ui.semantics.CollectionItemInfo
 import androidx.compose.ui.semantics.collectionInfo
@@ -34,6 +33,8 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastMapIndexed
 import kotlin.math.max
 
 /**
@@ -65,7 +66,11 @@
     spans: List<Int>,
     modifier: Modifier = Modifier,
     keys: (spanIndex: Int) -> Any = { it },
-    composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
+    composables:
+        @Composable
+        BoxScope.(
+            spanIndex: Int, row: Int, isFirstInColumn: Boolean, isLastInColumn: Boolean,
+        ) -> Unit,
 ) {
     SpannedGrid(
         primarySpaces = rows,
@@ -80,7 +85,7 @@
 }
 
 /**
- * Horizontal (non lazy) grid that supports [spans] for its elements.
+ * Vertical (non lazy) grid that supports [spans] for its elements.
  *
  * The elements will be laid down horizontally first, and then by rows. So assuming LTR layout, it
  * will be (for a span list `[2, 1, 2, 1, 1, 1, 1, 1]` and 4 columns):
@@ -107,7 +112,9 @@
     spans: List<Int>,
     modifier: Modifier = Modifier,
     keys: (spanIndex: Int) -> Any = { it },
-    composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
+    composables:
+        @Composable
+        BoxScope.(spanIndex: Int, column: Int, isFirstInRow: Boolean, isLastInRow: Boolean) -> Unit,
 ) {
     SpannedGrid(
         primarySpaces = columns,
@@ -130,7 +137,9 @@
     isVertical: Boolean,
     modifier: Modifier = Modifier,
     keys: (spanIndex: Int) -> Any = { it },
-    composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
+    composables:
+        @Composable
+        BoxScope.(spanIndex: Int, secondaryAxis: Int, isFirst: Boolean, isLast: Boolean) -> Unit,
 ) {
     val crossAxisArrangement = Arrangement.spacedBy(crossAxisSpacing)
     spans.forEachIndexed { index, span ->
@@ -139,7 +148,6 @@
                 "expected rance of [1, $primarySpaces]"
         }
     }
-
     if (isVertical) {
         check(crossAxisSpacing >= 0.dp) { "Negative columnSpacing $crossAxisSpacing" }
         check(mainAxisSpacing >= 0.dp) { "Negative rowSpacing $mainAxisSpacing" }
@@ -147,29 +155,30 @@
         check(mainAxisSpacing >= 0.dp) { "Negative columnSpacing $mainAxisSpacing" }
         check(crossAxisSpacing >= 0.dp) { "Negative rowSpacing $crossAxisSpacing" }
     }
-
-    val totalMainAxisGroups: Int =
+    // List of primary axis index to secondary axis index
+    // This is keyed to the size of the spans list for performance reasons as we don't expect the
+    // spans value to change outside of edit mode.
+    val positions = remember(spans.size) { Array(spans.size) { IntIntPair(0, 0) } }
+    val totalMainAxisGroups =
         remember(primarySpaces, spans) {
-            var currentAccumulated = 0
-            var groups = 1
-            spans.forEach { span ->
-                if (currentAccumulated + span <= primarySpaces) {
-                    currentAccumulated += span
-                } else {
-                    groups += 1
-                    currentAccumulated = span
+            var mainAxisGroup = 0
+            var currentSlot = 0
+            spans.fastForEachIndexed { index, span ->
+                if (currentSlot + span > primarySpaces) {
+                    currentSlot = 0
+                    mainAxisGroup += 1
                 }
+                positions[index] = IntIntPair(mainAxisGroup, currentSlot)
+                currentSlot += span
             }
-            groups
+            mainAxisGroup + 1
         }
-
     val slotPositionsAndSizesCache = remember {
         object {
             var sizes = IntArray(0)
             var positions = IntArray(0)
         }
     }
-
     Layout(
         {
             (0 until spans.size).map { spanIndex ->
@@ -184,7 +193,13 @@
                                 }
                         }
                     ) {
-                        composables(spanIndex)
+                        val position = positions[spanIndex]
+                        composables(
+                            spanIndex,
+                            position.second,
+                            position.second == 0,
+                            positions.getOrNull(spanIndex + 1)?.first != position.first,
+                        )
                     }
                 }
             }
@@ -205,7 +220,6 @@
             slotPositionsAndSizesCache.sizes,
         )
         val cellSizesInCrossAxis = slotPositionsAndSizesCache.sizes
-
         // with is needed because of the double receiver (Density, Arrangement).
         with(crossAxisArrangement) {
             arrange(
@@ -216,68 +230,73 @@
             )
         }
         val startPositions = slotPositionsAndSizesCache.positions
-
         val mainAxisSpacingPx = mainAxisSpacing.roundToPx()
         val mainAxisTotalGaps = (totalMainAxisGroups - 1) * mainAxisSpacingPx
-        val mainAxisSize = if (isVertical) constraints.maxHeight else constraints.maxWidth
+        val mainAxisMaxSize = if (isVertical) constraints.maxHeight else constraints.maxWidth
         val mainAxisElementConstraint =
-            if (mainAxisSize == Constraints.Infinity) {
+            if (mainAxisMaxSize == Constraints.Infinity) {
                 Constraints.Infinity
             } else {
-                max(0, (mainAxisSize - mainAxisTotalGaps) / totalMainAxisGroups)
+                max(0, (mainAxisMaxSize - mainAxisTotalGaps) / totalMainAxisGroups)
             }
 
-        val mainAxisSizes = IntArray(totalMainAxisGroups) { 0 }
-
-        var currentSlot = 0
-        var mainAxisGroup = 0
+        var mainAxisTotalSize = mainAxisTotalGaps
+        var currentMainAxis = 0
+        var currentMainAxisMax = 0
         val placeables =
-            measurables.mapIndexed { index, measurable ->
+            measurables.fastMapIndexed { index, measurable ->
                 val span = spans[index]
-                if (currentSlot + span > primarySpaces) {
-                    currentSlot = 0
-                    mainAxisGroup += 1
-                }
+                val position = positions[index]
                 val crossAxisConstraint =
-                    calculateWidth(cellSizesInCrossAxis, startPositions, currentSlot, span)
-                PlaceResult(
-                        measurable.measure(
-                            makeConstraint(
-                                isVertical,
-                                mainAxisElementConstraint,
-                                crossAxisConstraint,
-                            )
-                        ),
-                        currentSlot,
-                        mainAxisGroup,
+                    calculateWidth(cellSizesInCrossAxis, startPositions, position.second, span)
+
+                measurable
+                    .measure(
+                        makeConstraint(isVertical, mainAxisElementConstraint, crossAxisConstraint)
                     )
                     .also {
-                        currentSlot += span
-                        mainAxisSizes[mainAxisGroup] =
-                            max(
-                                mainAxisSizes[mainAxisGroup],
-                                if (isVertical) it.placeable.height else it.placeable.width,
-                            )
+                        val placeableSize = if (isVertical) it.height else it.width
+                        if (position.first != currentMainAxis) {
+                            // New row -- Add the max size to the total and reset the max
+                            mainAxisTotalSize += currentMainAxisMax
+                            currentMainAxisMax = placeableSize
+                            currentMainAxis = position.first
+                        } else {
+                            currentMainAxisMax = max(currentMainAxisMax, placeableSize)
+                        }
                     }
             }
+        mainAxisTotalSize += currentMainAxisMax
 
-        val mainAxisTotalSize = mainAxisTotalGaps + mainAxisSizes.sum()
-        val mainAxisStartingPoints =
-            mainAxisSizes.runningFold(0) { acc, value -> acc + value + mainAxisSpacingPx }
         val height = if (isVertical) mainAxisTotalSize else crossAxisSize
         val width = if (isVertical) crossAxisSize else mainAxisTotalSize
 
         layout(width, height) {
-            placeables.forEach { (placeable, slot, mainAxisGroup) ->
+            var previousMainAxis = 0
+            var currentMainAxisPosition = 0
+            var currentMainAxisMax = 0
+            placeables.forEachIndexed { index, placeable ->
+                val slot = positions[index].second
+                val mainAxisSize = if (isVertical) placeable.height else placeable.width
+
+                if (positions[index].first != previousMainAxis) {
+                    // Move up a row + padding
+                    currentMainAxisPosition += currentMainAxisMax + mainAxisSpacingPx
+                    currentMainAxisMax = mainAxisSize
+                    previousMainAxis = positions[index].first
+                } else {
+                    currentMainAxisMax = max(currentMainAxisMax, mainAxisSize)
+                }
+
                 val x =
                     if (isVertical) {
                         startPositions[slot]
                     } else {
-                        mainAxisStartingPoints[mainAxisGroup]
+                        currentMainAxisPosition
                     }
                 val y =
                     if (isVertical) {
-                        mainAxisStartingPoints[mainAxisGroup]
+                        currentMainAxisPosition
                     } else {
                         startPositions[slot]
                     }
@@ -321,9 +340,3 @@
         outArray[index] = slotSize + if (index < remainingPixels) 1 else 0
     }
 }
-
-private data class PlaceResult(
-    val placeable: Placeable,
-    val slotIndex: Int,
-    val mainAxisGroup: Int,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index cc0efbc..b4b3053 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2489,6 +2489,7 @@
                 Log.e(TAG,
                         "doKeyguard: already showing, but re-showing because we're interactive or "
                                 + "were in the middle of hiding.");
+                notifyLockNowCallback();
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 63cf4f7..ab0efed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -85,7 +85,7 @@
 
     val previewClock: Flow<ClockController> = keyguardClockRepository.previewClock
 
-    val clockEventController: ClockEventController by keyguardClockRepository::clockEventController
+    val clockEventController: ClockEventController = keyguardClockRepository.clockEventController
 
     var clock: ClockController? by keyguardClockRepository.clockEventController::clock
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index def1ac8..e81d535 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -81,22 +81,92 @@
                 }
 
                 if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+                    val xBuffer =
+                        keyguardRootView.context.resources.getDimensionPixelSize(
+                            R.dimen.smartspace_padding_horizontal
+                        )
+                    val yBuffer =
+                        keyguardRootView.context.resources.getDimensionPixelSize(
+                            R.dimen.smartspace_padding_vertical
+                        )
+
+                    val smallViewIds =
+                        listOf(sharedR.id.date_smartspace_view, sharedR.id.weather_smartspace_view)
+
+                    val largeViewIds =
+                        listOf(
+                            sharedR.id.date_smartspace_view_large,
+                            sharedR.id.weather_smartspace_view_large,
+                        )
+
                     launch("$TAG#smartspaceViewModel.burnInLayerVisibility") {
-                        keyguardRootViewModel.burnInLayerVisibility.collect { visibility ->
-                            if (clockViewModel.isLargeClockVisible.value) {
-                                // hide small clock date/weather
-                                val dateView =
-                                    keyguardRootView.requireViewById<View>(
-                                        sharedR.id.date_smartspace_view
-                                    )
-                                dateView.visibility = View.GONE
-                                val weatherView =
-                                    keyguardRootView.requireViewById<View>(
-                                        sharedR.id.weather_smartspace_view
-                                    )
-                                weatherView.visibility = View.GONE
+                        combine(
+                                keyguardRootViewModel.burnInLayerVisibility,
+                                clockViewModel.isLargeClockVisible,
+                                ::Pair,
+                            )
+                            .collect { (visibility, isLargeClock) ->
+                                if (isLargeClock) {
+                                    // hide small clock date/weather
+                                    for (viewId in smallViewIds) {
+                                        keyguardRootView.findViewById<View>(viewId)?.let {
+                                            it.visibility = View.GONE
+                                        }
+                                    }
+                                }
                             }
-                        }
+                    }
+
+                    launch("$TAG#clockEventController.onClockBoundsChanged") {
+                        // Whenever the doze amount changes, the clock may update it's view bounds.
+                        // We need to update our layout position as a result. We could do this via
+                        // `requestLayout`, but that's quite expensive when enclosed in since this
+                        // recomputes the entire ConstraintLayout, so instead we do it manually. We
+                        // would use translationX/Y for this, but that's used by burnin.
+                        combine(
+                                clockViewModel.isLargeClockVisible,
+                                clockViewModel.clockEventController.onClockBoundsChanged,
+                                ::Pair,
+                            )
+                            .collect { (isLargeClock, clockBounds) ->
+                                for (id in (if (isLargeClock) smallViewIds else largeViewIds)) {
+                                    keyguardRootView.findViewById<View>(id)?.let {
+                                        it.visibility = View.GONE
+                                    }
+                                }
+
+                                if (clockBounds == null) return@collect
+                                if (isLargeClock) {
+                                    val largeDateHeight =
+                                        keyguardRootView
+                                            .findViewById<View>(
+                                                sharedR.id.date_smartspace_view_large
+                                            )
+                                            ?.height ?: 0
+                                    for (id in largeViewIds) {
+                                        keyguardRootView.findViewById<View>(id)?.let { view ->
+                                            val viewHeight = view.height
+                                            val offset = (largeDateHeight - viewHeight) / 2
+                                            view.top =
+                                                (clockBounds.bottom + yBuffer + offset).toInt()
+                                            view.bottom = view.top + viewHeight
+                                        }
+                                    }
+                                } else {
+                                    for (id in smallViewIds) {
+                                        keyguardRootView.findViewById<View>(id)?.let { view ->
+                                            val viewWidth = view.width
+                                            if (view.isLayoutRtl()) {
+                                                view.right = (clockBounds.left - xBuffer).toInt()
+                                                view.left = view.right - viewWidth
+                                            } else {
+                                                view.left = (clockBounds.right + xBuffer).toInt()
+                                                view.right = view.left + viewWidth
+                                            }
+                                        }
+                                    }
+                                }
+                            }
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 37cc852f..d0b5f74 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -226,7 +226,7 @@
                         ConstraintSet.TOP,
                         customR.id.lockscreen_clock_view_large,
                         ConstraintSet.BOTTOM,
-                        dateWeatherPaddingStart,
+                        context.resources.getDimensionPixelSize(R.dimen.smartspace_padding_vertical),
                     )
 
                     connect(
@@ -291,7 +291,9 @@
                         ConstraintSet.START,
                         customR.id.lockscreen_clock_view,
                         ConstraintSet.END,
-                        20,
+                        context.resources.getDimensionPixelSize(
+                            R.dimen.smartspace_padding_horizontal
+                        ),
                     )
                     connect(
                         sharedR.id.date_smartspace_view,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
index 4001054..c088900 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
@@ -17,33 +17,39 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.Flags
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.BlurConfig
 import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class GlanceableHubToPrimaryBouncerTransitionViewModel
 @Inject
 constructor(
     private val blurConfig: BlurConfig,
     animationFlow: KeyguardTransitionAnimationFlow,
-    communalSettingsInteractor: CommunalSettingsInteractor,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
+    private val communalSceneInteractor: CommunalSceneInteractor,
+    private val keyguardStateController: KeyguardStateController,
 ) : PrimaryBouncerTransition {
     private val transitionAnimation =
         animationFlow
             .setup(
-                duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+                duration = FromGlanceableHubTransitionInteractor.TO_BOUNCER_DURATION,
                 edge = Edge.INVALID,
             )
             .setupWithoutSceneContainer(edge = Edge.create(GLANCEABLE_HUB, PRIMARY_BOUNCER))
@@ -59,6 +65,13 @@
             transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
         }
 
+    /** Whether to delay the animation to fade in bouncer elements. */
+    fun willDelayAppearAnimation(isLandscape: Boolean): Boolean =
+        communalSettingsInteractor.isV2FlagEnabled() &&
+            communalSceneInteractor.isIdleOnCommunal.value &&
+            !keyguardStateController.isKeyguardScreenRotationAllowed() &&
+            isLandscape
+
     override val notificationBlurRadius: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0.0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index cf5cc26..dcbf7b5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.res.Resources
 import androidx.constraintlayout.helper.widget.Layer
+import com.android.keyguard.ClockEventController
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.customization.R as customR
 import com.android.systemui.dagger.SysUISingleton
@@ -68,6 +69,7 @@
                 initialValue = true,
             )
 
+    val clockEventController: ClockEventController = keyguardClockInteractor.clockEventController
     val currentClock = keyguardClockInteractor.currentClock
 
     val hasCustomWeatherDataDisplay =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
index eb6f979..b80e6b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
@@ -35,11 +35,16 @@
     index: Int,
     column: Int,
     columns: Int,
+    isFirstInRow: Boolean,
+    isLastInRow: Boolean,
 ): BounceableInfo {
-    // Only look for neighbor bounceables if they are on the same row
+    // A tile may be the last in the row without being on the last column
     val onLastColumn = sizedTile.onLastColumn(column, columns)
-    val previousTile = getOrNull(index - 1)?.takeIf { column != 0 }
-    val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn }
+
+    // Only look for neighbor bounceables if they are on the same row
+    val previousTile = getOrNull(index - 1)?.takeIf { !isFirstInRow }
+    val nextTile = getOrNull(index + 1)?.takeIf { !isLastInRow }
+
     return BounceableInfo(this[index], previousTile, nextTile, !onLastColumn)
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index 495870f..cdc03bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -57,7 +57,6 @@
         onDispose { tiles.forEach { it.stopListening(token) } }
     }
     val columns = viewModel.columns
-    var cellIndex = 0
     Box(modifier = modifier) {
         GridAnchor()
         VerticalSpannedGrid(
@@ -67,17 +66,23 @@
             spans = spans,
             modifier = Modifier.sysuiResTag("qqs_tile_layout"),
             keys = { sizedTiles[it].tile.spec },
-        ) { spanIndex ->
+        ) { spanIndex, column, isFirstInColumn, isLastInColumn ->
             val it = sizedTiles[spanIndex]
-            val column = cellIndex % columns
-            cellIndex += it.width
             Element(it.tile.spec.toElementKey(spanIndex), Modifier) {
                 Tile(
                     tile = it.tile,
                     iconOnly = it.isIcon,
                     squishiness = { squishiness },
                     coroutineScope = scope,
-                    bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
+                    bounceableInfo =
+                        bounceables.bounceableInfo(
+                            it,
+                            index = spanIndex,
+                            column = column,
+                            columns = columns,
+                            isFirstInRow = isFirstInColumn,
+                            isLastInRow = isLastInColumn,
+                        ),
                     tileHapticsViewModelFactoryProvider =
                         viewModel.tileHapticsViewModelFactoryProvider,
                     // There should be no QuickQuickSettings when the details view is enabled.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index dfee4976..0503049 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -85,8 +85,6 @@
             remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } }
         val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
         val scope = rememberCoroutineScope()
-        var cellIndex = 0
-
         val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } }
 
         VerticalSpannedGrid(
@@ -95,10 +93,9 @@
             rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical),
             spans = spans,
             keys = { sizedTiles[it].tile.spec },
-        ) { spanIndex ->
+        ) { spanIndex, column, isFirstInColumn, isLastInColumn ->
             val it = sizedTiles[spanIndex]
-            val column = cellIndex % columns
-            cellIndex += it.width
+
             Element(it.tile.spec.toElementKey(spanIndex), Modifier) {
                 Tile(
                     tile = it.tile,
@@ -106,7 +103,15 @@
                     squishiness = { squishiness },
                     tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider,
                     coroutineScope = scope,
-                    bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
+                    bounceableInfo =
+                        bounceables.bounceableInfo(
+                            it,
+                            index = spanIndex,
+                            column = column,
+                            columns = columns,
+                            isFirstInRow = isFirstInColumn,
+                            isLastInRow = isLastInColumn,
+                        ),
                     detailsViewModel = detailsViewModel,
                 )
             }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index e4cd7ea..305444f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -451,6 +451,8 @@
             } else {
                 mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
             }
+        } else if (state.glanceableHubOrientationAware) {
+            mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
         } else {
             mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
         }
@@ -627,6 +629,7 @@
                 state.shadeOrQsExpanded,
                 state.notificationShadeFocusable,
                 state.glanceableHubShowing,
+                state.glanceableHubOrientationAware,
                 state.bouncerShowing,
                 state.keyguardFadingAway,
                 state.keyguardGoingAway,
@@ -763,6 +766,12 @@
     }
 
     @Override
+    public void setGlanceableHubOrientationAware(boolean isOrientationAware) {
+        mCurrentState.glanceableHubOrientationAware = isOrientationAware;
+        apply(mCurrentState);
+    }
+
+    @Override
     public void setBackdropShowing(boolean showing) {
         mCurrentState.mediaBackdropShowing = showing;
         apply(mCurrentState);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index 6a4b52a..a1eac74 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -36,6 +36,7 @@
     @JvmField var notificationShadeFocusable: Boolean = false,
     @JvmField var bouncerShowing: Boolean = false,
     @JvmField var glanceableHubShowing: Boolean = false,
+    @JvmField var glanceableHubOrientationAware: Boolean = false,
     @JvmField var keyguardFadingAway: Boolean = false,
     @JvmField var keyguardGoingAway: Boolean = false,
     @JvmField var qsExpanded: Boolean = false,
@@ -81,6 +82,7 @@
             notificationShadeFocusable.toString(),
             bouncerShowing.toString(),
             glanceableHubShowing.toString(),
+            glanceableHubOrientationAware.toString(),
             keyguardFadingAway.toString(),
             keyguardGoingAway.toString(),
             qsExpanded.toString(),
@@ -122,6 +124,7 @@
             panelExpanded: Boolean,
             notificationShadeFocusable: Boolean,
             glanceableHubShowing: Boolean,
+            glanceableHubOrientationAware: Boolean,
             bouncerShowing: Boolean,
             keyguardFadingAway: Boolean,
             keyguardGoingAway: Boolean,
@@ -153,6 +156,7 @@
                 this.shadeOrQsExpanded = panelExpanded
                 this.notificationShadeFocusable = notificationShadeFocusable
                 this.glanceableHubShowing = glanceableHubShowing
+                this.glanceableHubOrientationAware = glanceableHubOrientationAware
                 this.bouncerShowing = bouncerShowing
                 this.keyguardFadingAway = keyguardFadingAway
                 this.keyguardGoingAway = keyguardGoingAway
@@ -202,6 +206,7 @@
                 "panelExpanded",
                 "notificationShadeFocusable",
                 "glanceableHubShowing",
+                "glanceableHubOrientationAware",
                 "bouncerShowing",
                 "keyguardFadingAway",
                 "keyguardGoingAway",
@@ -223,7 +228,7 @@
                 "dozing",
                 "scrimsVisibility",
                 "backgroundBlurRadius",
-                "communalVisible"
+                "communalVisible",
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 246177e..8114685 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -334,7 +334,7 @@
                         } else if (state.fromContent == overlay) {
                             state.progress.map { progress -> 1 - progress }
                         } else {
-                            flowOf(0f)
+                            state.currentOverlays().map { if (overlay in it) 1f else 0f }
                         }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index 6ebe024..c2e355d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -72,6 +72,7 @@
         mRow = row;
 
         final IconComparator iconVisibilityComparator = new IconComparator() {
+            @Override
             public boolean compare(View parent, View child, Object parentData,
                     Object childData) {
                 if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
@@ -83,6 +84,7 @@
             }
         };
         final IconComparator greyComparator = new IconComparator() {
+            @Override
             public boolean compare(View parent, View child, Object parentData,
                     Object childData) {
                 if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 85fad42..50cf015 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -89,6 +89,9 @@
     /** Sets the state of whether the glanceable hub is showing or not. */
     default void setGlanceableHubShowing(boolean showing) {}
 
+    /** Sets the state of whether the glanceable hub can change with user's orientation or not. */
+    default void setGlanceableHubOrientationAware(boolean isOrientationAware) {}
+
     /** Sets the state of whether the backdrop is showing or not. */
     default void setBackdropShowing(boolean showing) {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/BundleHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/BundleHeaderViewModel.kt
new file mode 100644
index 0000000..d02ae43
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/BundleHeaderViewModel.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2025 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.statusbar.notification.row.ui.viewmodel
+
+import android.graphics.drawable.Drawable
+import android.view.View
+import androidx.compose.animation.core.tween
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MotionScheme
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.notifications.ui.composable.row.BundleHeader
+import kotlinx.coroutines.CoroutineScope
+
+interface BundleHeaderViewModel {
+    val titleText: String
+    val numberOfChildren: Int?
+    val bundleIcon: Drawable?
+    val previewIcons: List<Drawable>
+
+    val state: SceneTransitionLayoutState
+
+    val hasUnreadMessages: Boolean
+    val backgroundDrawable: Drawable?
+
+    fun onHeaderClicked(scope: CoroutineScope)
+}
+
+class BundleHeaderViewModelImpl : BundleHeaderViewModel {
+    override var titleText by mutableStateOf("")
+    override var numberOfChildren by mutableStateOf<Int?>(1)
+    override var hasUnreadMessages by mutableStateOf(true)
+    override var bundleIcon by mutableStateOf<Drawable?>(null)
+    override var previewIcons by mutableStateOf(listOf<Drawable>())
+    override var backgroundDrawable by mutableStateOf<Drawable?>(null)
+
+    var onExpandClickListener: View.OnClickListener? = null
+
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    override var state: MutableSceneTransitionLayoutState =
+        MutableSceneTransitionLayoutState(
+            BundleHeader.Scenes.Collapsed,
+            MotionScheme.standard(),
+            transitions {
+                from(BundleHeader.Scenes.Collapsed, to = BundleHeader.Scenes.Expanded) {
+                    spec = tween(500)
+                    translate(BundleHeader.Elements.PreviewIcon3, x = 32.dp)
+                    translate(BundleHeader.Elements.PreviewIcon2, x = 16.dp)
+                    fade(BundleHeader.Elements.PreviewIcon1)
+                    fade(BundleHeader.Elements.PreviewIcon2)
+                    fade(BundleHeader.Elements.PreviewIcon3)
+                }
+            },
+        )
+
+    override fun onHeaderClicked(scope: CoroutineScope) {
+        val targetScene =
+            when (state.currentScene) {
+                BundleHeader.Scenes.Collapsed -> BundleHeader.Scenes.Expanded
+                BundleHeader.Scenes.Expanded -> BundleHeader.Scenes.Collapsed
+                else -> error("Unknown Scene")
+            }
+        state.setTargetScene(targetScene, scope)
+
+        onExpandClickListener?.onClick(null)
+        hasUnreadMessages = false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 4390f1b..1a17b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1226,6 +1226,14 @@
     }
 
     @Override
+    public void setOccluded(boolean isOccluded) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+        this.setVisibility(isOccluded ? View.INVISIBLE : View.VISIBLE);
+    }
+
+    @Override
     public void setScrollState(@NonNull ShadeScrollState scrollState) {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index a7305f7..9c855e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -49,6 +49,9 @@
     /** Max alpha for this view */
     fun setMaxAlpha(alpha: Float)
 
+    /** Set whether this view is occluded by something else. */
+    fun setOccluded(isOccluded: Boolean)
+
     /** Sets a clipping shape, which defines the drawable area of this view. */
     fun setClippingShape(shape: ShadeScrimShape?)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index a4e39cb..653344a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -86,6 +86,8 @@
                     .collectTraced { view.setClippingShape(it) }
             }
 
+            launch { viewModel.isOccluded.collectTraced { view.setOccluded(it) } }
+
             launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } }
             launch { viewModel.shadeScrollState.collect { view.setScrollState(it) } }
             launch {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 1dbaf2f..a277597 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -24,7 +24,9 @@
 import com.android.compose.animation.scene.ObservableTransitionState.Idle
 import com.android.compose.animation.scene.ObservableTransitionState.Transition
 import com.android.compose.animation.scene.ObservableTransitionState.Transition.ChangeScene
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -70,6 +72,7 @@
     private val stackAppearanceInteractor: NotificationStackAppearanceInteractor,
     shadeInteractor: ShadeInteractor,
     shadeModeInteractor: ShadeModeInteractor,
+    bouncerInteractor: BouncerInteractor,
     private val remoteInputInteractor: RemoteInputInteractor,
     private val sceneInteractor: SceneInteractor,
     // TODO(b/336364825) Remove Lazy when SceneContainerFlag is released -
@@ -131,12 +134,15 @@
     private fun expandFractionDuringOverlayTransition(
         transition: Transition,
         currentScene: SceneKey,
+        currentOverlays: Set<OverlayKey>,
         shadeExpansion: Float,
     ): Float {
         return if (currentScene == Scenes.Lockscreen) {
             1f
         } else if (transition.isTransitioningFromOrTo(Overlays.NotificationsShade)) {
             shadeExpansion
+        } else if (Overlays.NotificationsShade in currentOverlays) {
+            1f
         } else {
             0f
         }
@@ -161,12 +167,13 @@
                 shadeInteractor.qsExpansion,
                 shadeModeInteractor.shadeMode,
                 sceneInteractor.transitionState,
-            ) { shadeExpansion, qsExpansion, _, transitionState ->
+                sceneInteractor.currentOverlays,
+            ) { shadeExpansion, qsExpansion, _, transitionState, currentOverlays ->
                 when (transitionState) {
                     is Idle ->
                         if (
                             expandedInScene(transitionState.currentScene) ||
-                                Overlays.NotificationsShade in transitionState.currentOverlays
+                                Overlays.NotificationsShade in currentOverlays
                         ) {
                             1f
                         } else {
@@ -182,12 +189,14 @@
                         expandFractionDuringOverlayTransition(
                             transition = transitionState,
                             currentScene = transitionState.currentScene,
+                            currentOverlays = currentOverlays,
                             shadeExpansion = shadeExpansion,
                         )
                     is Transition.ReplaceOverlay ->
                         expandFractionDuringOverlayTransition(
                             transition = transitionState,
                             currentScene = transitionState.currentScene,
+                            currentOverlays = currentOverlays,
                             shadeExpansion = shadeExpansion,
                         )
                 }
@@ -198,6 +207,12 @@
     val qsExpandFraction: Flow<Float> =
         shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction")
 
+    val isOccluded: Flow<Boolean> =
+        bouncerInteractor.bouncerExpansion
+            .map { it == 1f }
+            .distinctUntilChanged()
+            .dumpWhileCollecting("isOccluded")
+
     /** Blur radius to be applied to Notifications. */
     fun blurRadius(maxBlurRadius: Flow<Int>) =
         combine(blurFraction, maxBlurRadius) { fraction, maxRadius -> fraction * maxRadius }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 33cc62c..9d55e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -21,6 +21,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.app.tracing.coroutines.flow.flowName
 import com.android.systemui.Flags.glanceableHubV2
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -106,7 +107,6 @@
 import kotlinx.coroutines.flow.combineTransform
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
@@ -132,6 +132,7 @@
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val shadeInteractor: ShadeInteractor,
+    private val bouncerInteractor: BouncerInteractor,
     shadeModeInteractor: ShadeModeInteractor,
     notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
     private val alternateBouncerToGoneTransitionViewModel:
@@ -516,8 +517,13 @@
                             combineTransform(
                                 shadeInteractor.shadeExpansion,
                                 shadeInteractor.qsExpansion,
-                            ) { shadeExpansion, qsExpansion ->
-                                if (qsExpansion == 1f) {
+                                bouncerInteractor.bouncerExpansion,
+                            ) { shadeExpansion, qsExpansion, bouncerExpansion ->
+                                if (bouncerExpansion == 1f) {
+                                    emit(0f)
+                                } else if (bouncerExpansion > 0f) {
+                                    emit(1 - bouncerExpansion)
+                                } else if (qsExpansion == 1f) {
                                     // Ensure HUNs will be visible in QS shade (at least while
                                     // unlocked)
                                     emit(1f)
@@ -526,19 +532,36 @@
                                     emit(1f - qsExpansion)
                                 }
                             }
-                        Split -> isAnyExpanded.filter { it }.map { 1f }
+                        Split ->
+                            combineTransform(isAnyExpanded, bouncerInteractor.bouncerExpansion) {
+                                isAnyExpanded,
+                                bouncerExpansion ->
+                                if (bouncerExpansion == 1f) {
+                                    emit(0f)
+                                } else if (bouncerExpansion > 0f) {
+                                    emit(1 - bouncerExpansion)
+                                } else if (isAnyExpanded) {
+                                    emit(1f)
+                                }
+                            }
                         Dual ->
                             combineTransform(
                                 shadeModeInteractor.isShadeLayoutWide,
                                 headsUpNotificationInteractor.get().isHeadsUpOrAnimatingAway,
                                 shadeInteractor.shadeExpansion,
                                 shadeInteractor.qsExpansion,
+                                bouncerInteractor.bouncerExpansion,
                             ) {
                                 isShadeLayoutWide,
                                 isHeadsUpOrAnimatingAway,
                                 shadeExpansion,
-                                qsExpansion ->
-                                if (isShadeLayoutWide) {
+                                qsExpansion,
+                                bouncerExpansion ->
+                                if (bouncerExpansion == 1f) {
+                                    emit(0f)
+                                } else if (bouncerExpansion > 0f) {
+                                    emit(1 - bouncerExpansion)
+                                } else if (isShadeLayoutWide) {
                                     if (shadeExpansion > 0f) {
                                         emit(1f)
                                     }
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
index 33e1929..952d40e 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
@@ -17,8 +17,13 @@
 package com.android.systemui.wallpapers
 
 import android.app.Flags
+import android.content.res.Configuration.UI_MODE_NIGHT_MASK
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
 import android.graphics.Canvas
+import android.graphics.Color
 import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
 import android.graphics.RadialGradient
 import android.graphics.Shader
 import android.service.wallpaper.WallpaperService
@@ -74,9 +79,9 @@
                         .toFloat()
                 val totalHeight = destRectF.height() + (offsetPx * 2)
                 val leftCenterX = -offsetPx
-                val leftCenterY = -offsetPx
+                val leftCenterY = totalHeight - offsetPx
                 val rightCenterX = offsetPx + destRectF.width()
-                val rightCenterY = totalHeight - offsetPx
+                val rightCenterY = -offsetPx
                 val radius = (destRectF.width() / 2) + offsetPx
 
                 canvas.drawCircle(
@@ -112,6 +117,28 @@
                             )
                     },
                 )
+
+                val isDarkMode =
+                    context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK ==
+                        UI_MODE_NIGHT_YES
+                val maskColor =
+                    ColorUtils.setAlphaComponent(
+                        if (isDarkMode) Color.BLACK else Color.WHITE,
+                        /* alpha= */ 87, // 0.34f * 255
+                    )
+                val maskPaint =
+                    Paint().apply {
+                        xfermode =
+                            PorterDuffXfermode(
+                                if (isDarkMode) {
+                                    PorterDuff.Mode.DARKEN
+                                } else {
+                                    PorterDuff.Mode.LIGHTEN
+                                }
+                            )
+                        color = maskColor
+                    }
+                canvas.drawRect(destRectF, maskPaint)
             } catch (exception: IllegalStateException) {
                 Log.d(TAG, "Fail to draw in the canvas", exception)
             } finally {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index f822ee9..f18d73d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -194,7 +194,7 @@
 
     @Test
     fun clockSet_validateInitialization() {
-        verify(clock).initialize(any(), anyFloat(), anyFloat())
+        verify(clock).initialize(any(), anyFloat(), anyFloat(), any())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
index 9ae5715..2fc81eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
@@ -231,6 +231,11 @@
                 actionInteractorImpl.onActionIconClick(inAudioSharingMediaDeviceItem) {}
                 assertThat(bluetoothTileDialogAudioSharingRepository.audioSharingStarted)
                     .isEqualTo(false)
+                verify(bluetoothTileDialogLogger)
+                    .logAudioSharingButtonClick(
+                        AudioSharingButtonClick.CHECK_MARK,
+                        inAudioSharingMediaDeviceItem,
+                    )
             }
         }
     }
@@ -243,6 +248,11 @@
                 actionInteractorImpl.onActionIconClick(connectedAudioSharingMediaDeviceItem) {}
                 assertThat(bluetoothTileDialogAudioSharingRepository.audioSharingStarted)
                     .isEqualTo(true)
+                verify(bluetoothTileDialogLogger)
+                    .logAudioSharingButtonClick(
+                        AudioSharingButtonClick.PLUS_BUTTON,
+                        connectedAudioSharingMediaDeviceItem,
+                    )
             }
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index f3af794f..1269b23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -390,30 +390,12 @@
     fun testControllersCreatedAndInitialized() {
         verify(variableDateViewController).init()
 
-        verify(batteryMeterViewController).init()
-        verify(batteryMeterViewController).ignoreTunerUpdates()
-
         val inOrder = Mockito.inOrder(mShadeCarrierGroupControllerBuilder)
         inOrder.verify(mShadeCarrierGroupControllerBuilder).setShadeCarrierGroup(carrierGroup)
         inOrder.verify(mShadeCarrierGroupControllerBuilder).build()
     }
 
     @Test
-    fun batteryModeControllerCalledWhenQsExpandedFractionChanges() {
-        whenever(qsBatteryModeController.getBatteryMode(Mockito.same(null), eq(0f)))
-            .thenReturn(BatteryMeterView.MODE_ON)
-        whenever(qsBatteryModeController.getBatteryMode(Mockito.same(null), eq(1f)))
-            .thenReturn(BatteryMeterView.MODE_ESTIMATE)
-        shadeHeaderController.qsVisible = true
-
-        val times = 10
-        repeat(times) { shadeHeaderController.qsExpandedFraction = it / (times - 1).toFloat() }
-
-        verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ON)
-        verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
-    }
-
-    @Test
     fun testClockPivotLtr() {
         val width = 200
         whenever(clock.width).thenReturn(width)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 3c4aecc..f8b35a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.statusbar.BatteryStatusChip
+import com.android.systemui.statusbar.core.NewStatusBarIcons
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut
@@ -118,11 +119,11 @@
     }
 
     @Test
-    fun testBatteryStatusEvent_standardAnimationLifecycle() = runTest {
+    fun testStatusEvent_standardAnimationLifecycle() = runTest {
         // Instantiate class under test with TestScope from runTest
-        initializeSystemStatusAnimationScheduler(testScope = this)
+        initializeSystemStatusAnimationScheduler(this)
 
-        val batteryChip = createAndScheduleFakeBatteryEvent()
+        val eventChip = createAndScheduleFakeEvent()
 
         // assert that animation is queued
         assertEquals(AnimationQueued, systemStatusAnimationScheduler.animationState.value)
@@ -131,8 +132,7 @@
         advanceTimeBy(DEBOUNCE_DELAY + 1)
         // status chip starts animating in after debounce delay
         assertEquals(AnimatingIn, systemStatusAnimationScheduler.animationState.value)
-        assertEquals(0f, batteryChip.contentView.alpha)
-        assertEquals(0f, batteryChip.view.alpha)
+        assertEquals(0f, eventChip.view.alpha)
         verify(listener, times(1)).onSystemEventAnimationBegin()
 
         // skip appear animation
@@ -140,23 +140,20 @@
         advanceTimeBy(APPEAR_ANIMATION_DURATION)
         // assert that status chip is visible
         assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
-        assertEquals(1f, batteryChip.contentView.alpha)
-        assertEquals(1f, batteryChip.view.alpha)
+        assertEquals(1f, eventChip.view.alpha)
 
         // skip status chip display time
         advanceTimeBy(DISPLAY_LENGTH + 1)
         // assert that it is still visible but switched to the AnimatingOut state
         assertEquals(AnimatingOut, systemStatusAnimationScheduler.animationState.value)
-        assertEquals(1f, batteryChip.contentView.alpha)
-        assertEquals(1f, batteryChip.view.alpha)
+        assertEquals(1f, eventChip.view.alpha)
         verify(listener, times(1)).onSystemEventAnimationFinish(false)
 
         // skip disappear animation
         animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
         // assert that it is not visible anymore
         assertEquals(Idle, systemStatusAnimationScheduler.animationState.value)
-        assertEquals(0f, batteryChip.contentView.alpha)
-        assertEquals(0f, batteryChip.view.alpha)
+        assertEquals(0f, eventChip.view.alpha)
     }
 
     /** Regression test for b/294104969. */
@@ -226,8 +223,8 @@
         initializeSystemStatusAnimationScheduler(testScope = this)
 
         // create and schedule low priority event
-        val batteryChip = createAndScheduleFakeBatteryEvent()
-        batteryChip.view.alpha = 0f
+        val eventChip = createAndScheduleFakeEvent()
+        eventChip.view.alpha = 0f
 
         // assert that animation is queued
         assertEquals(AnimationQueued, systemStatusAnimationScheduler.animationState.value)
@@ -244,7 +241,7 @@
         // high priority status chip is visible while low priority status chip is not visible
         assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
         assertEquals(1f, privacyChip.view.alpha)
-        assertEquals(0f, batteryChip.view.alpha)
+        assertEquals(0f, eventChip.view.alpha)
     }
 
     @Test
@@ -253,14 +250,14 @@
         initializeSystemStatusAnimationScheduler(testScope = this)
 
         // create and schedule low priority event
-        val batteryChip = createAndScheduleFakeBatteryEvent()
+        val eventChip = createAndScheduleFakeEvent()
 
         // fast forward to RunningChipAnim state
         fastForwardAnimationToState(RunningChipAnim)
 
         // assert that chip is displayed
         assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
-        assertEquals(1f, batteryChip.view.alpha)
+        assertEquals(1f, eventChip.view.alpha)
 
         // create and schedule high priority event
         val privacyChip = createAndScheduleFakePrivacyEvent()
@@ -284,7 +281,7 @@
         // high priority status chip is visible while low priority status chip is not visible
         assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
         assertEquals(1f, privacyChip.view.alpha)
-        assertEquals(0f, batteryChip.view.alpha)
+        assertEquals(0f, eventChip.view.alpha)
     }
 
     @Test
@@ -293,7 +290,7 @@
         initializeSystemStatusAnimationScheduler(testScope = this)
 
         // create and schedule low priority event
-        val batteryChip = createAndScheduleFakeBatteryEvent()
+        val eventChip = createAndScheduleFakeEvent()
 
         // skip debounce delay
         advanceTimeBy(DEBOUNCE_DELAY + 1)
@@ -331,7 +328,7 @@
         // high priority status chip is visible while low priority status chip is not visible
         assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
         assertEquals(1f, privacyChip.view.alpha)
-        assertEquals(0f, batteryChip.view.alpha)
+        assertEquals(0f, eventChip.view.alpha)
     }
 
     @Test
@@ -343,8 +340,8 @@
         val privacyChip = createAndScheduleFakePrivacyEvent()
 
         // create and schedule low priority event
-        val batteryChip = createAndScheduleFakeBatteryEvent()
-        batteryChip.view.alpha = 0f
+        val eventChip = createAndScheduleFakeEvent()
+        eventChip.view.alpha = 0f
 
         // skip debounce delay and appear animation
         advanceTimeBy(DEBOUNCE_DELAY + APPEAR_ANIMATION_DURATION + 1)
@@ -353,7 +350,7 @@
         // high priority status chip is visible while low priority status chip is not visible
         assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
         assertEquals(1f, privacyChip.view.alpha)
-        assertEquals(0f, batteryChip.view.alpha)
+        assertEquals(0f, eventChip.view.alpha)
     }
 
     @Test
@@ -649,12 +646,17 @@
         systemStatusAnimationScheduler.onStatusEvent(fakeEvent)
     }
 
-    private fun createAndScheduleFakeBatteryEvent(): BatteryStatusChip {
-        val batteryChip = BatteryStatusChip(mContext)
-        val fakeBatteryEvent =
-            FakeStatusEvent(viewCreator = { batteryChip }, priority = 50, forceVisible = false)
-        systemStatusAnimationScheduler.onStatusEvent(fakeBatteryEvent)
-        return batteryChip
+    private fun createAndScheduleFakeEvent(): BackgroundAnimatableView {
+        val eventChip =
+            if (NewStatusBarIcons.isEnabled) {
+                BGImageView(mContext)
+            } else {
+                BatteryStatusChip(mContext)
+            }
+        val fakeStatusEvent =
+            FakeStatusEvent(viewCreator = { eventChip }, priority = 50, forceVisible = false)
+        systemStatusAnimationScheduler.onStatusEvent(fakeStatusEvent)
+        return eventChip
     }
 
     private fun initializeSystemStatusAnimationScheduler(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
index b4fbaad..5f34420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
@@ -18,7 +18,6 @@
 
 import android.app.Flags
 import android.content.Context
-import android.content.res.Resources
 import android.graphics.Canvas
 import android.graphics.Paint
 import android.graphics.Rect
@@ -43,6 +42,7 @@
 import org.mockito.Mockito.spy
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.verifyZeroInteractions
@@ -61,23 +61,20 @@
 
     @Mock private lateinit var mockContext: Context
 
-    @Mock private lateinit var mockResources: Resources
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        val spyResources = spy(context.resources)
+
         whenever(surfaceHolder.surface).thenReturn(surface)
         whenever(surfaceHolder.surfaceFrame).thenReturn(surfaceFrame)
         whenever(surface.lockHardwareCanvas()).thenReturn(canvas)
         whenever(mockContext.getColor(anyInt())).thenReturn(1)
-        whenever(mockContext.resources).thenReturn(mockResources)
-        whenever(
-                mockResources.getDimensionPixelOffset(
-                    eq(R.dimen.gradient_color_wallpaper_center_offset)
-                )
-            )
-            .thenReturn(OFFSET_PX)
+        whenever(mockContext.resources).thenReturn(spyResources)
+        doReturn(OFFSET_PX)
+            .`when`(spyResources)
+            .getDimensionPixelOffset(eq(R.dimen.gradient_color_wallpaper_center_offset))
     }
 
     private fun createGradientColorWallpaperEngine(): Engine {
@@ -106,7 +103,8 @@
 
         engine.onSurfaceRedrawNeeded(surfaceHolder)
 
-        verify(canvas).drawRect(any<RectF>(), any<Paint>())
+        // One rect for the background, one rect for the foreground mask.
+        verify(canvas, times(2)).drawRect(any<RectF>(), any<Paint>())
         verify(canvas, times(2)).drawCircle(anyFloat(), anyFloat(), anyFloat(), any<Paint>())
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
index e0c0fbd..bc8e62c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
@@ -26,6 +26,7 @@
             applicationContext,
             localBluetoothManager,
             bluetoothTileDialogAudioSharingRepository,
+            bluetoothTileDialogLogger,
             testDispatcher,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
index c744eac..0f6f191 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
@@ -28,7 +28,7 @@
 
     private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
-    private val mutableAudioSourceStateUpdate = MutableSharedFlow<Unit>()
+    private val mutableAudioSourceStateUpdate = MutableSharedFlow<Unit>(replay = 1)
 
     var sourceAdded: Boolean = false
         private set
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
index d27ecce..94d27f7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.log.sessionTracker
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneBackInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 
 val Kosmos.bouncerInteractor by Fixture {
     BouncerInteractor(
@@ -39,6 +40,7 @@
         powerInteractor = powerInteractor,
         uiEventLogger = uiEventLogger,
         sessionTracker = sessionTracker,
+        sceneInteractor = sceneInteractor,
         sceneBackInteractor = sceneBackInteractor,
         configurationInteractor = configurationInteractor,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt
index b233d3f..c6f55f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt
@@ -16,16 +16,20 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.blurConfig
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.policy.keyguardStateController
 
 val Kosmos.glanceableHubToPrimaryBouncerTransitionViewModel by Fixture {
     GlanceableHubToPrimaryBouncerTransitionViewModel(
         animationFlow = keyguardTransitionAnimationFlow,
         blurConfig = blurConfig,
         communalSettingsInteractor = communalSettingsInteractor,
+        communalSceneInteractor = communalSceneInteractor,
+        keyguardStateController = keyguardStateController,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 02cf1f5..dff9f3a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -87,6 +88,7 @@
 import com.android.systemui.statusbar.policy.configurationController
 import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
+import com.android.systemui.statusbar.policy.keyguardStateController
 import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
 import com.android.systemui.util.time.systemClock
 import com.android.systemui.volume.domain.interactor.volumeDialogInteractor
@@ -126,6 +128,7 @@
     val keyguardInteractor by lazy { kosmos.keyguardInteractor }
     val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
     val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
+    val keyguardStateController by lazy { kosmos.keyguardStateController }
     val keyguardStatusBarViewModel by lazy { kosmos.keyguardStatusBarViewModel }
     val powerRepository by lazy { kosmos.fakePowerRepository }
     val clock by lazy { kosmos.systemClock }
@@ -147,6 +150,7 @@
     val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
     val communalInteractor by lazy { kosmos.communalInteractor }
     val communalSceneInteractor by lazy { kosmos.communalSceneInteractor }
+    val communalSettingsInteractor by lazy { kosmos.communalSettingsInteractor }
     val sceneContainerPlugin by lazy { kosmos.sceneContainerPlugin }
     val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor }
     val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
index 167b11d..87ce501 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.dump.dumpManager
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
@@ -32,6 +33,7 @@
         stackAppearanceInteractor = notificationStackAppearanceInteractor,
         shadeInteractor = shadeInteractor,
         shadeModeInteractor = shadeModeInteractor,
+        bouncerInteractor = bouncerInteractor,
         remoteInputInteractor = remoteInputInteractor,
         sceneInteractor = sceneInteractor,
         keyguardInteractor = { keyguardInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 17ef208..85fe3d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
 import android.content.applicationContext
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.dump.dumpManager
@@ -74,6 +75,7 @@
         keyguardInteractor = keyguardInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         shadeInteractor = shadeInteractor,
+        bouncerInteractor = bouncerInteractor,
         shadeModeInteractor = shadeModeInteractor,
         notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
         alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
diff --git a/proto/src/metrics_constants/OWNERS b/proto/src/metrics_constants/OWNERS
index b032841..169f887 100644
--- a/proto/src/metrics_constants/OWNERS
+++ b/proto/src/metrics_constants/OWNERS
@@ -1,3 +1,2 @@
 cwren@android.com
 yaochen@google.com
-yro@google.com
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index bb3c710..0f6f86b 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -103,12 +103,16 @@
                 @Override
                 public void toggleAutoclickPause(boolean paused) {
                     if (paused) {
-                        if (mClickScheduler != null) {
-                            mClickScheduler.cancel();
-                        }
-                        if (mAutoclickIndicatorScheduler != null) {
-                            mAutoclickIndicatorScheduler.cancel();
-                        }
+                        cancelPendingClick();
+                    }
+                }
+
+                @Override
+                public void onHoverChange(boolean hovered) {
+                    // Cancel all pending clicks when the mouse moves outside the panel while
+                    // autoclick is still paused.
+                    if (!hovered && isPaused()) {
+                        cancelPendingClick();
                     }
                 }
             };
@@ -226,8 +230,17 @@
     }
 
     private boolean isPaused() {
-        // TODO (b/397460424): Unpause when hovering over panel.
-        return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused();
+        return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused()
+                && !mAutoclickTypePanel.isHovered();
+    }
+
+    private void cancelPendingClick() {
+        if (mClickScheduler != null) {
+            mClickScheduler.cancel();
+        }
+        if (mAutoclickIndicatorScheduler != null) {
+            mAutoclickIndicatorScheduler.cancel();
+        }
     }
 
     @VisibleForTesting
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java
new file mode 100644
index 0000000..fe8adf7
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2025 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.server.accessibility.autoclick;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+
+/**
+ * A custom LinearLayout that provides enhanced hover event handling.
+ * This class overrides hover methods to track hover events for the entire panel ViewGroup,
+ * including the descendant buttons. This allows for consistent hover behavior and feedback
+ * across the entire layout.
+ */
+public class AutoclickLinearLayout extends LinearLayout {
+    public interface OnHoverChangedListener {
+        /**
+         * Called when the hover state of the AutoclickLinearLayout changes.
+         *
+         * @param hovered {@code true} if the view is now hovered, {@code false} otherwise.
+         */
+        void onHoverChanged(boolean hovered);
+    }
+
+    private OnHoverChangedListener mListener;
+
+    public AutoclickLinearLayout(Context context) {
+        super(context);
+    }
+
+    public AutoclickLinearLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AutoclickLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public AutoclickLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public void setOnHoverChangedListener(OnHoverChangedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public boolean onInterceptHoverEvent(MotionEvent event) {
+        int action = event.getActionMasked();
+        setHovered(action == MotionEvent.ACTION_HOVER_ENTER
+                || action == MotionEvent.ACTION_HOVER_MOVE);
+
+        return false;
+    }
+
+    @Override
+    public void onHoverChanged(boolean hovered) {
+        super.onHoverChanged(hovered);
+
+        if (mListener != null) {
+            mListener.onHoverChanged(hovered);
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
index ab4b3b1..57bbb4a 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -110,11 +110,18 @@
          * @param paused {@code true} to pause autoclick, {@code false} to resume.
          */
         void toggleAutoclickPause(boolean paused);
+
+        /**
+         * Called when the hovered state of the panel changes.
+         *
+         * @param hovered {@code true} if the panel is now hovered, {@code false} otherwise.
+         */
+        void onHoverChange(boolean hovered);
     }
 
     private final Context mContext;
 
-    private final View mContentView;
+    private final AutoclickLinearLayout mContentView;
 
     private final WindowManager mWindowManager;
 
@@ -164,8 +171,9 @@
                 R.drawable.accessibility_autoclick_resume);
 
         mContentView =
-                LayoutInflater.from(context)
+                (AutoclickLinearLayout) LayoutInflater.from(context)
                         .inflate(R.layout.accessibility_autoclick_type_panel, null);
+        mContentView.setOnHoverChangedListener(mClickPanelController::onHoverChange);
         mLeftClickButton =
                 mContentView.findViewById(R.id.accessibility_autoclick_left_click_layout);
         mRightClickButton =
@@ -339,6 +347,10 @@
         return mPaused;
     }
 
+    public boolean isHovered() {
+        return mContentView.isHovered();
+    }
+
     /** Toggles the panel expanded or collapsed state. */
     private void togglePanelExpansion(@AutoclickType int clickType) {
         final LinearLayout button = getButtonFromClickType(clickType);
@@ -520,7 +532,7 @@
 
     @VisibleForTesting
     @NonNull
-    View getContentViewForTesting() {
+    AutoclickLinearLayout getContentViewForTesting() {
         return mContentView;
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index fd230f6..fb32943 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -887,6 +887,9 @@
                     mSendHoverEnterAndMoveDelayed.cancel();
                     mSendHoverExitDelayed.cancel();
                 }
+                if (pointerIndex < 0) {
+                    return;
+                }
                 // If the user is touch exploring the second pointer may be
                 // performing a double tap to activate an item without need
                 // for the user to lift their exploring finger.
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
index 84402c8..12c35ae 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
@@ -177,6 +177,8 @@
      */
     abstract void writeAndClearOldAccessHistory();
 
+    void shutdown() {}
+
     /** Remove all discrete op events. */
     abstract void clearHistory();
 
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
index 604cb30..dc11be9 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
@@ -57,13 +57,18 @@
 public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
     private static final String TAG = "DiscreteOpsSqlRegistry";
 
+    private static final long DB_WRITE_INTERVAL = Duration.ofMinutes(10).toMillis();
+    private static final long EXPIRED_ENTRY_DELETION_INTERVAL = Duration.ofHours(6).toMillis();
+
+    // Event type handled by SqliteWriteHandler
+    private static final int WRITE_DATABASE_RECURRING = 1;
+    private static final int DELETE_EXPIRED_ENTRIES = 2;
+    private static final int WRITE_DATABASE_CACHE_FULL = 3;
+
     private final Context mContext;
     private final DiscreteOpsDbHelper mDiscreteOpsDbHelper;
     private final SqliteWriteHandler mSqliteWriteHandler;
     private final DiscreteOpCache mDiscreteOpCache = new DiscreteOpCache(512);
-    private static final long THREE_HOURS = Duration.ofHours(3).toMillis();
-    private static final int WRITE_CACHE_EVICTED_OP_EVENTS = 1;
-    private static final int DELETE_OLD_OP_EVENTS = 2;
     // Attribution chain id is used to identify an attribution source chain, This is
     // set for startOp only. PermissionManagerService resets this ID on device restart, so
     // we use previously persisted chain id as offset, and add it to chain id received from
@@ -83,6 +88,9 @@
         mSqliteWriteHandler = new SqliteWriteHandler(thread.getLooper());
         mDiscreteOpsDbHelper = new DiscreteOpsDbHelper(context, databaseFile);
         mChainIdOffset = mDiscreteOpsDbHelper.getLargestAttributionChainId();
+        mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING, DB_WRITE_INTERVAL);
+        mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES,
+                EXPIRED_ENTRY_DELETION_INTERVAL);
     }
 
     @Override
@@ -117,15 +125,14 @@
     }
 
     @Override
-    void writeAndClearOldAccessHistory() {
-        // Let the sql impl also follow the same disk write frequencies as xml,
-        // controlled by AppOpsService.
+    void shutdown() {
+        mSqliteWriteHandler.removeAllPendingMessages();
         mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear());
-        if (!mSqliteWriteHandler.hasMessages(DELETE_OLD_OP_EVENTS)) {
-            if (mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_OLD_OP_EVENTS, THREE_HOURS)) {
-                Slog.w(TAG, "DELETE_OLD_OP_EVENTS is not queued");
-            }
-        }
+    }
+
+    @Override
+    void writeAndClearOldAccessHistory() {
+        // no-op
     }
 
     @Override
@@ -175,7 +182,7 @@
             @Nullable String attributionTagFilter, int opFlagsFilter,
             Set<String> attributionExemptPkgs) {
         // flush the cache into database before read.
-        writeAndClearOldAccessHistory();
+        mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear());
         boolean assembleChains = attributionExemptPkgs != null;
         IntArray opCodes = getAppOpCodes(filter, opNamesFilter);
         beginTimeMillis = Math.max(beginTimeMillis, Instant.now().minus(sDiscreteHistoryCutoff,
@@ -363,20 +370,59 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case WRITE_CACHE_EVICTED_OP_EVENTS:
-                    List<DiscreteOp> opEvents = (List<DiscreteOp>) msg.obj;
-                    mDiscreteOpsDbHelper.insertDiscreteOps(opEvents);
-                    break;
-                case DELETE_OLD_OP_EVENTS:
+                case WRITE_DATABASE_RECURRING -> {
+                    try {
+                        List<DiscreteOp> evictedEvents;
+                        synchronized (mDiscreteOpCache) {
+                            evictedEvents = mDiscreteOpCache.evict();
+                        }
+                        mDiscreteOpsDbHelper.insertDiscreteOps(evictedEvents);
+                    } finally {
+                        mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING,
+                                DB_WRITE_INTERVAL);
+                        // Schedule a cleanup to truncate older (before cutoff time) entries.
+                        if (!mSqliteWriteHandler.hasMessages(DELETE_EXPIRED_ENTRIES)) {
+                            mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES,
+                                    EXPIRED_ENTRY_DELETION_INTERVAL);
+                        }
+                    }
+                }
+                case DELETE_EXPIRED_ENTRIES -> {
                     long cutOffTimeStamp = System.currentTimeMillis() - sDiscreteHistoryCutoff;
                     mDiscreteOpsDbHelper.execSQL(
                             DiscreteOpsTable.DELETE_TABLE_DATA_BEFORE_ACCESS_TIME,
                             new Object[]{cutOffTimeStamp});
-                    break;
-                default:
-                    throw new IllegalStateException("Unexpected value: " + msg.what);
+                }
+                case WRITE_DATABASE_CACHE_FULL -> {
+                    try {
+                        List<DiscreteOp> evictedEvents;
+                        synchronized (mDiscreteOpCache) {
+                            evictedEvents = mDiscreteOpCache.evict();
+                            // if nothing to evict, just write the whole cache to database.
+                            if (evictedEvents.isEmpty()
+                                    && mDiscreteOpCache.size() >= mDiscreteOpCache.capacity()) {
+                                evictedEvents.addAll(mDiscreteOpCache.mCache);
+                                mDiscreteOpCache.clear();
+                            }
+                        }
+                        mDiscreteOpsDbHelper.insertDiscreteOps(evictedEvents);
+                    } finally {
+                        // Just in case initial message is not scheduled.
+                        if (!mSqliteWriteHandler.hasMessages(WRITE_DATABASE_RECURRING)) {
+                            mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING,
+                                    DB_WRITE_INTERVAL);
+                        }
+                    }
+                }
+                default -> throw new IllegalStateException("Unexpected value: " + msg.what);
             }
         }
+
+        void removeAllPendingMessages() {
+            removeMessages(WRITE_DATABASE_RECURRING);
+            removeMessages(DELETE_EXPIRED_ENTRIES);
+            removeMessages(WRITE_DATABASE_CACHE_FULL);
+        }
     }
 
     /**
@@ -390,6 +436,7 @@
      * 4) During shutdown.
      */
     class DiscreteOpCache {
+        private static final String TAG = "DiscreteOpCache";
         private final int mCapacity;
         private final ArraySet<DiscreteOp> mCache;
 
@@ -404,23 +451,9 @@
                     return;
                 }
                 mCache.add(opEvent);
+
                 if (mCache.size() >= mCapacity) {
-                    if (DEBUG_LOG) {
-                        Slog.i(TAG, "Current discrete ops cache size: " + mCache.size());
-                    }
-                    List<DiscreteOp> evictedEvents = evict();
-                    if (DEBUG_LOG) {
-                        Slog.i(TAG, "Evicted discrete ops size: " + evictedEvents.size());
-                    }
-                    // if nothing to evict, just write the whole cache to disk
-                    if (evictedEvents.isEmpty()) {
-                        Slog.w(TAG, "No discrete ops event is evicted, write cache to db.");
-                        evictedEvents.addAll(mCache);
-                        mCache.clear();
-                    }
-                    Message msg = mSqliteWriteHandler.obtainMessage(
-                            WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents);
-                    mSqliteWriteHandler.sendMessage(msg);
+                    mSqliteWriteHandler.sendEmptyMessage(WRITE_DATABASE_CACHE_FULL);
                 }
             }
         }
@@ -461,6 +494,14 @@
             }
         }
 
+        int size() {
+            return mCache.size();
+        }
+
+        int capacity() {
+            return mCapacity;
+        }
+
         /**
          * Remove all entries from the cache.
          */
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 928a4b2..d267e0d 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -750,6 +750,7 @@
         }
         // Do not call persistPendingHistory inside the memory lock, due to possible deadlock
         persistPendingHistory();
+        mDiscreteRegistry.shutdown();
     }
 
     void persistPendingHistory() {
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 02f817e..6e5308e 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -35,6 +35,7 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -130,8 +131,10 @@
             mUngroupedAbuseNotifications = new ArrayMap<>();
 
     // Contains the list of group summaries that were canceled when "singleton groups" were
-    // force grouped. Used to remove the original group's children when an app cancels the
-    // already removed summary. Key is userId|packageName|g:OriginalGroupName
+    // force grouped. Key is userId|packageName|g:OriginalGroupName. Used to:
+    // 1) remove the original group's children when an app cancels the already removed summary.
+    // 2) perform the same side effects that would happen if the group is removed because
+    //    all its force-regrouped children are removed (e.g. firing its deleteIntent).
     @GuardedBy("mAggregatedNotifications")
     private final ArrayMap<FullyQualifiedGroupKey, CachedSummary>
             mCanceledSummaries = new ArrayMap<>();
@@ -278,7 +281,11 @@
     public void onNotificationRemoved(NotificationRecord record) {
         try {
             if (notificationForceGrouping()) {
-                onNotificationRemoved(record, new ArrayList<>());
+                Slog.wtf(TAG,
+                        "This overload of onNotificationRemoved() should not be called if "
+                                + "notification_force_grouping is enabled!",
+                        new Exception("call stack"));
+                onNotificationRemoved(record, new ArrayList<>(), false);
             } else {
                 final StatusBarNotification sbn = record.getSbn();
                 maybeUngroup(sbn, true, sbn.getUserId());
@@ -926,10 +933,12 @@
      *
      * @param record the removed notification
      * @param notificationList the full notification list from NotificationManagerService
+     * @param sendingDelete whether the removed notification is being removed in a way that sends
+     *                     its {@code deleteIntent}
      */
     @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
     protected void onNotificationRemoved(final NotificationRecord record,
-                final List<NotificationRecord> notificationList) {
+            final List<NotificationRecord> notificationList, boolean sendingDelete) {
         final StatusBarNotification sbn = record.getSbn();
         final String pkgName = sbn.getPackageName();
         final int userId = record.getUserId();
@@ -973,9 +982,11 @@
                 }
 
                 // Try to cleanup cached summaries if notification was canceled (not snoozed)
+                // If the notification was cancelled by an action that fires its delete intent,
+                // also fire it for the cached summary.
                 if (record.isCanceled) {
                     maybeClearCanceledSummariesCache(pkgName, userId,
-                            record.getNotification().getGroup(), notificationList);
+                            record.getNotification().getGroup(), notificationList, sendingDelete);
                 }
             }
         }
@@ -1759,13 +1770,18 @@
     private void cacheCanceledSummary(NotificationRecord record) {
         final FullyQualifiedGroupKey groupKey = new FullyQualifiedGroupKey(record.getUserId(),
                 record.getSbn().getPackageName(), record.getNotification().getGroup());
-        mCanceledSummaries.put(groupKey, new CachedSummary(record.getSbn().getId(),
-                record.getSbn().getTag(), record.getNotification().getGroup(), record.getKey()));
+        mCanceledSummaries.put(groupKey, new CachedSummary(
+                record.getSbn().getId(),
+                record.getSbn().getTag(),
+                record.getNotification().getGroup(),
+                record.getKey(),
+                record.getNotification().deleteIntent));
     }
 
     @GuardedBy("mAggregatedNotifications")
     private void maybeClearCanceledSummariesCache(String pkgName, int userId,
-            String groupName, List<NotificationRecord> notificationList) {
+            String groupName, List<NotificationRecord> notificationList,
+            boolean sendSummaryDelete) {
         final FullyQualifiedGroupKey findKey = new FullyQualifiedGroupKey(userId, pkgName,
                 groupName);
         CachedSummary summary = mCanceledSummaries.get(findKey);
@@ -1786,6 +1802,9 @@
             }
             if (!stillHasChildren) {
                 removeCachedSummary(pkgName, userId, summary);
+                if (sendSummaryDelete && summary.deleteIntent != null) {
+                    mCallback.sendAppProvidedSummaryDeleteIntent(pkgName, summary.deleteIntent);
+                }
             }
         }
     }
@@ -1965,7 +1984,8 @@
         }
     }
 
-    record CachedSummary(int id, String tag, String originalGroupKey, String key) {}
+    record CachedSummary(int id, String tag, String originalGroupKey, String key,
+                         @Nullable PendingIntent deleteIntent) { }
 
     protected static class NotificationAttributes {
         public final int flags;
@@ -2035,6 +2055,15 @@
         // New callbacks for API abuse grouping
         void removeAppProvidedSummary(String key);
 
+        /**
+         * Send a cached summary's deleteIntent, when the last of its original children is removed.
+         *
+         * <p>While technically the group summary was "canceled" much earlier (because it was the
+         * summary of a sparse group and its children got reparented), the posting package expected
+         * the summary's deleteIntent to fire when the summary is auto-dismissed.
+         */
+        void sendAppProvidedSummaryDeleteIntent(String pkg, PendingIntent deleteIntent);
+
         void removeNotificationFromCanceledGroup(int userId, String pkg, String groupKey,
                 int cancelReason);
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6fddfb5..60371d7 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3211,6 +3211,11 @@
             }
 
             @Override
+            public void sendAppProvidedSummaryDeleteIntent(String pkg, PendingIntent deleteIntent) {
+                sendDeleteIntent(deleteIntent, pkg);
+            }
+
+            @Override
             public void removeNotificationFromCanceledGroup(int userId, String pkg,
                     String groupKey, int cancelReason) {
                 synchronized (mNotificationLock) {
@@ -9898,7 +9903,8 @@
                             if (notificationForceGrouping()) {
                                 mHandler.post(() -> {
                                     synchronized (mNotificationLock) {
-                                        mGroupHelper.onNotificationRemoved(r, mNotificationList);
+                                        mGroupHelper.onNotificationRemoved(r, mNotificationList,
+                                                /* sendingDelete= */ false);
                                     }
                                 });
                             } else {
@@ -10826,20 +10832,7 @@
 
         // tell the app
         if (sendDelete) {
-            final PendingIntent deleteIntent = r.getNotification().deleteIntent;
-            if (deleteIntent != null) {
-                try {
-                    // make sure deleteIntent cannot be used to start activities from background
-                    LocalServices.getService(ActivityManagerInternal.class)
-                            .clearPendingIntentAllowBgActivityStarts(deleteIntent.getTarget(),
-                                    ALLOWLIST_TOKEN);
-                    deleteIntent.send();
-                } catch (PendingIntent.CanceledException ex) {
-                    // do nothing - there's no relevant way to recover, and
-                    //     no reason to let this propagate
-                    Slog.w(TAG, "canceled PendingIntent for " + r.getSbn().getPackageName(), ex);
-                }
-            }
+            sendDeleteIntent(r.getNotification().deleteIntent, r.getSbn().getPackageName());
         }
 
         // Only cancel these if this notification actually got to be posted.
@@ -10854,7 +10847,7 @@
                     mHandler.removeCallbacksAndEqualMessages(r.getKey());
                     mHandler.post(() -> {
                         synchronized (NotificationManagerService.this.mNotificationLock) {
-                            mGroupHelper.onNotificationRemoved(r, mNotificationList);
+                            mGroupHelper.onNotificationRemoved(r, mNotificationList, sendDelete);
                         }
                     });
 
@@ -10952,6 +10945,21 @@
         }
     }
 
+    private static void sendDeleteIntent(@Nullable PendingIntent deleteIntent, String fromPkg) {
+        if (deleteIntent != null) {
+            try {
+                // make sure deleteIntent cannot be used to start activities from background
+                LocalServices.getService(ActivityManagerInternal.class)
+                        .clearPendingIntentAllowBgActivityStarts(deleteIntent.getTarget(),
+                                ALLOWLIST_TOKEN);
+                deleteIntent.send();
+            } catch (PendingIntent.CanceledException ex) {
+                // There's no relevant way to recover, and no reason to let this propagate
+                Slog.w(TAG, "canceled PendingIntent for " + fromPkg, ex);
+            }
+        }
+    }
+
     @VisibleForTesting
     void updateUriPermissions(@Nullable NotificationRecord newRecord,
             @Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 0fc182f..46ff798 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -42,6 +42,7 @@
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE;
 import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE;
 
 import android.annotation.FlaggedApi;
@@ -155,6 +156,7 @@
     private static final String ATT_VERSION = "version";
     private static final String ATT_NAME = "name";
     private static final String ATT_UID = "uid";
+    private static final String ATT_LAST_BUBBLES_VERSION_UPGRADE = "last_bubbles_version_upgrade";
 
     private static final String ATT_USERID = "userid";
     private static final String ATT_ID = "id";
@@ -286,7 +288,8 @@
         if (!TAG_RANKING.equals(tag)) return;
 
         final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
-        boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
+        boolean upgradeForBubbles = parser.getAttributeInt(null,
+                ATT_LAST_BUBBLES_VERSION_UPGRADE, -1) < Build.VERSION.SDK_INT;
         boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION);
         if (mShowReviewPermissionsNotification
                 && (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION)) {
@@ -337,15 +340,19 @@
             }
             boolean skipWarningLogged = false;
             boolean skipGroupWarningLogged = false;
-            boolean hasSAWPermission = false;
-            if (upgradeForBubbles && uid != UNKNOWN_UID) {
-                hasSAWPermission = mAppOps.noteOpNoThrow(
-                        OP_SYSTEM_ALERT_WINDOW, uid, name, null,
-                        "check-notif-bubble") == AppOpsManager.MODE_ALLOWED;
+            int bubblePref = parser.getAttributeInt(null, ATT_ALLOW_BUBBLE,
+                    DEFAULT_BUBBLE_PREFERENCE);
+            boolean bubbleLocked = (parser.getAttributeInt(null,
+                    ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS) & USER_LOCKED_BUBBLE)
+                    != 0;
+            if (!bubbleLocked
+                    && upgradeForBubbles
+                    && uid != UNKNOWN_UID
+                    && mAppOps.noteOpNoThrow(OP_SYSTEM_ALERT_WINDOW, uid, name, null,
+                    "check-notif-bubble") == AppOpsManager.MODE_ALLOWED) {
+                // User hasn't changed bubble pref & the app has SAW, so allow all bubbles.
+                bubblePref = BUBBLE_PREFERENCE_ALL;
             }
-            int bubblePref = hasSAWPermission
-                    ? BUBBLE_PREFERENCE_ALL
-                    : parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE);
             int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
 
             // when data is loaded from disk it's loaded as USER_ALL, but restored data that
@@ -684,6 +691,7 @@
     public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException {
         out.startTag(null, TAG_RANKING);
         out.attributeInt(null, ATT_VERSION, XML_VERSION);
+        out.attributeInt(null, ATT_LAST_BUBBLES_VERSION_UPGRADE, Build.VERSION.SDK_INT);
         if (mHideSilentStatusBarIcons != DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS) {
             out.startTag(null, TAG_STATUS_ICONS);
             out.attributeBoolean(null, ATT_HIDE_SILENT, mHideSilentStatusBarIcons);
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index f51e60c..36686fc 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -312,7 +312,6 @@
         private final @ActivityManager.ProcessState int mCallingUidProcState;
         private final boolean mIsCallingUidPersistentSystemProcess;
         final BackgroundStartPrivileges mBalAllowedByPiSender;
-        final BackgroundStartPrivileges mBalAllowedByPiCreatorWithHardening;
         final BackgroundStartPrivileges mBalAllowedByPiCreator;
         private final String mRealCallingPackage;
         private final int mRealCallingUid;
@@ -379,22 +378,14 @@
 
             if (mAutoOptInCaller) {
                 // grant BAL privileges unless explicitly opted out
-                mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
+                mBalAllowedByPiCreator =
                         callerBackgroundActivityStartMode == MODE_BACKGROUND_ACTIVITY_START_DENIED
                                 ? BackgroundStartPrivileges.NONE
                                 : BackgroundStartPrivileges.ALLOW_BAL;
             } else {
                 // for PendingIntents we restrict BAL based on target_sdk
-                mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator(
+                mBalAllowedByPiCreator = getBackgroundStartPrivilegesAllowedByCreator(
                         callingUid, callingPackage, checkedOptions);
-                final BackgroundStartPrivileges mBalAllowedByPiCreatorWithoutHardening =
-                        callerBackgroundActivityStartMode
-                                == MODE_BACKGROUND_ACTIVITY_START_DENIED
-                                ? BackgroundStartPrivileges.NONE
-                                : BackgroundStartPrivileges.ALLOW_BAL;
-                mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator()
-                        ? mBalAllowedByPiCreatorWithHardening
-                        : mBalAllowedByPiCreatorWithoutHardening;
             }
 
             if (mAutoOptInReason != null) {
@@ -585,9 +576,8 @@
             if (mCallerApp != null) {
                 sb.append("; inVisibleTask: ").append(mCallerApp.hasActivityInVisibleTask());
             }
-            sb.append("; balAllowedByPiCreator: ").append(mBalAllowedByPiCreator);
-            sb.append("; balAllowedByPiCreatorWithHardening: ")
-                    .append(mBalAllowedByPiCreatorWithHardening);
+            sb.append("; balAllowedByPiCreator: ")
+                    .append(mBalAllowedByPiCreator);
             if (mResultForCaller != null) {
                 sb.append("; resultIfPiCreatorAllowsBal: ")
                         .append(balCodeToString(mResultForCaller.mCode));
@@ -638,14 +628,13 @@
     }
 
     static class BalVerdict {
-        static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked");
+        static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, "Blocked");
         static final BalVerdict ALLOW_BY_DEFAULT =
-                new BalVerdict(BAL_ALLOW_DEFAULT, false, "Default");
+                new BalVerdict(BAL_ALLOW_DEFAULT, "Default");
         // Careful using this - it will bypass all ASM checks.
         static final BalVerdict ALLOW_PRIVILEGED =
-                new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, false, "PRIVILEGED");
+                new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, "PRIVILEGED");
         private final @BalCode int mCode;
-        private final boolean mBackground;
         private final String mMessage;
         private String mProcessInfo;
         // indicates BAL would be blocked because only creator of the PI has the privilege to allow
@@ -654,8 +643,7 @@
         /** indicates that this verdict is based on the real calling UID and not the calling UID */
         private boolean mBasedOnRealCaller;
 
-        BalVerdict(@BalCode int balCode, boolean background, String message) {
-            this.mBackground = background;
+        BalVerdict(@BalCode int balCode, String message) {
             this.mCode = balCode;
             this.mMessage = message;
         }
@@ -708,16 +696,7 @@
                     builder.append(" [realCaller]");
                 }
                 if (DEBUG_ACTIVITY_STARTS) {
-                    builder.append(" (");
-                    if (mBackground) {
-                        builder.append("Background ");
-                    }
-                    builder.append("Activity start ");
-                    if (mCode == BAL_BLOCK) {
-                        builder.append("denied");
-                    } else {
-                        builder.append("allowed: ").append(mMessage);
-                    }
+                    builder.append(" (").append(mMessage);
                     if (mProcessInfo != null) {
                         builder.append(" ");
                         builder.append(mProcessInfo);
@@ -795,7 +774,6 @@
             // to realCallingUid when calculating resultForRealCaller below.
             if (getService().hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) {
                 state.setResultForRealCaller(new BalVerdict(BAL_ALLOW_SDK_SANDBOX,
-                        /*background*/ false,
                         "uid in SDK sandbox has visible (non-toast) window"));
                 return allowBasedOnRealCaller(state);
             }
@@ -1059,8 +1037,7 @@
                 || state.mAppSwitchState == APP_SWITCH_FG_ONLY
                 || isHomeApp(state.mCallingUid, state.mCallingPackage);
         if (appSwitchAllowedOrFg && state.mCallingUidHasVisibleActivity) {
-            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
-                    /*background*/ false, "callingUid has visible window");
+            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "callingUid has visible window");
         }
         return BalVerdict.BLOCK;
     };
@@ -1068,7 +1045,7 @@
     private final BalExemptionCheck mCheckCallerNonAppVisible = state -> {
         if (state.mCallingUidHasNonAppVisibleWindow) {
             return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
-                    /*background*/ false, "callingUid has non-app visible window "
+                    "callingUid has non-app visible window "
                     + getService().mActiveUids.getNonAppVisibleWindowDetails(state.mCallingUid));
         }
         return BalVerdict.BLOCK;
@@ -1080,9 +1057,7 @@
         if (state.mCallingUid == Process.ROOT_UID
                 || callingAppId == Process.SYSTEM_UID
                 || callingAppId == Process.NFC_UID) {
-            return new BalVerdict(
-                    BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false,
-                    "Important callingUid");
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, "Important callingUid");
         }
         return BalVerdict.BLOCK;
     };
@@ -1090,9 +1065,7 @@
     private final BalExemptionCheck mCheckCallerIsAllowlistedComponent = state -> {
         // Always allow home application to start activities.
         if (isHomeApp(state.mCallingUid, state.mCallingPackage)) {
-            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                    /*background*/ false,
-                    "Home app");
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Home app");
         }
 
         final int callingAppId = UserHandle.getAppId(state.mCallingUid);
@@ -1100,37 +1073,31 @@
         final WindowState imeWindow =
                 getService().mRootWindowContainer.getCurrentInputMethodWindow();
         if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) {
-            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                    /*background*/ false,
-                    "Active ime");
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Active ime");
         }
 
         // don't abort if the callingUid is a persistent system process
         if (state.mIsCallingUidPersistentSystemProcess) {
             return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                    /*background*/ false, "callingUid is persistent system process");
+                    "callingUid is persistent system process");
         }
 
         // don't abort if the caller has the same uid as the recents component
         if (getSupervisor().mRecentTasks.isCallerRecents(state.mCallingUid)) {
-            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                    /*background*/ true, "Recents Component");
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Recents Component");
         }
         // don't abort if the callingUid is the device owner
         if (getService().isDeviceOwner(state.mCallingUid)) {
-            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                    /*background*/ true, "Device Owner");
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Device Owner");
         }
         // don't abort if the callingUid is a affiliated profile owner
         if (getService().isAffiliatedProfileOwner(state.mCallingUid)) {
-            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                    /*background*/ true, "Affiliated Profile Owner");
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Affiliated Profile Owner");
         }
         // don't abort if the callingUid has companion device
         final int callingUserId = UserHandle.getUserId(state.mCallingUid);
         if (getService().isAssociatedCompanionApp(callingUserId, state.mCallingUid)) {
-            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                    /*background*/ true, "Companion App");
+            return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Companion App");
         }
         return BalVerdict.BLOCK;
     };
@@ -1139,7 +1106,6 @@
         // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
         if (hasBalPermission(state.mCallingUid, state.mCallingPid)) {
             return new BalVerdict(BAL_ALLOW_PERMISSION,
-                    /*background*/ true,
                     "START_ACTIVITIES_FROM_BACKGROUND permission granted");
         }
         return BalVerdict.BLOCK;
@@ -1149,7 +1115,7 @@
         if (getService().hasSystemAlertWindowPermission(state.mCallingUid, state.mCallingPid,
                 state.mCallingPackage)) {
             return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
-                    /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
+                    "SYSTEM_ALERT_WINDOW permission is granted");
         }
         return BalVerdict.BLOCK;
     };
@@ -1159,7 +1125,7 @@
         if (isSystemExemptFlagEnabled() && getService().getAppOpsManager().checkOpNoThrow(
                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
                 state.mCallingUid, state.mCallingPackage) == AppOpsManager.MODE_ALLOWED) {
-            return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
+            return new BalVerdict(BAL_ALLOW_PERMISSION,
                     "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted");
         }
         return BalVerdict.BLOCK;
@@ -1200,8 +1166,7 @@
                 || state.mAppSwitchState == APP_SWITCH_FG_ONLY
                 || isHomeApp(state.mRealCallingUid, state.mRealCallingPackage);
         if (appSwitchAllowedOrFg && state.mRealCallingUidHasVisibleActivity) {
-            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
-                    /*background*/ false, "realCallingUid has visible window");
+            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "realCallingUid has visible window");
         }
         return BalVerdict.BLOCK;
     };
@@ -1209,9 +1174,9 @@
     private final BalExemptionCheck mCheckRealCallerNonAppVisible = state -> {
         if (state.mRealCallingUidHasNonAppVisibleWindow) {
             return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
-                    /*background*/ false, "realCallingUid has non-app visible window "
-                    + getService().mActiveUids.getNonAppVisibleWindowDetails(
-                    state.mRealCallingUid));
+                    "realCallingUid has non-app visible window "
+                            + getService().mActiveUids.getNonAppVisibleWindowDetails(
+                            state.mRealCallingUid));
         }
         return BalVerdict.BLOCK;
     };
@@ -1230,9 +1195,7 @@
                 == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
         if (allowAlways
                 && hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) {
-            return new BalVerdict(BAL_ALLOW_PERMISSION,
-                    /*background*/ false,
-                    "realCallingUid has BAL permission.");
+            return new BalVerdict(BAL_ALLOW_PERMISSION, "realCallingUid has BAL permission.");
         }
         return BalVerdict.BLOCK;
     };
@@ -1245,7 +1208,7 @@
                 && getService().hasSystemAlertWindowPermission(state.mRealCallingUid,
                 state.mRealCallingPid, state.mRealCallingPackage)) {
             return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
-                    /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
+                    "SYSTEM_ALERT_WINDOW permission is granted");
         }
         return BalVerdict.BLOCK;
     };
@@ -1258,7 +1221,6 @@
         if ((allowAlways || state.mAllowBalExemptionForSystemProcess)
                 && state.mIsRealCallingUidPersistentSystemProcess) {
             return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID,
-                    /*background*/ false,
                     "realCallingUid is persistent system process AND intent "
                             + "sender forced to allow.");
         }
@@ -1270,7 +1232,6 @@
         if (getService().isAssociatedCompanionApp(
                 UserHandle.getUserId(state.mRealCallingUid), state.mRealCallingUid)) {
             return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
-                    /*background*/ false,
                     "realCallingUid is a companion app.");
         }
         return BalVerdict.BLOCK;
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index ccf1aed..31b2394 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -125,27 +125,27 @@
             long lastActivityFinishTime) {
         // Allow if the proc is instrumenting with background activity starts privs.
         if (checkConfiguration.checkOtherExemptions && hasBackgroundActivityStartPrivileges) {
-            return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
+            return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/
                     "process instrumenting with background activity starts privileges");
         }
         // Allow if the flag was explicitly set.
         if (checkConfiguration.checkOtherExemptions && isBackgroundStartAllowedByToken(uid,
                 packageName, checkConfiguration.isCheckingForFgsStart)) {
             return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_TOKEN : BAL_ALLOW_PERMISSION,
-                    /*background*/ true, "process allowed by token");
+                    /*background*/  "process allowed by token");
         }
         // Allow if the caller is bound by a UID that's currently foreground.
         // But still respect the appSwitchState.
         if (checkConfiguration.checkVisibility && appSwitchState != APP_SWITCH_DISALLOW
                 && isBoundByForegroundUid()) {
             return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND
-                    : BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
+                    : BAL_ALLOW_VISIBLE_WINDOW, /*background*/
                     "process bound by foreground uid");
         }
         // Allow if the caller has an activity in any foreground task.
         if (checkConfiguration.checkOtherExemptions && hasActivityInVisibleTask
                 && appSwitchState != APP_SWITCH_DISALLOW) {
-            return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false,
+            return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/
                     "process has activity in foreground task");
         }
 
@@ -160,7 +160,7 @@
                 long timeSinceLastStartOrFinish = now - Math.max(lastActivityLaunchTime,
                         lastActivityFinishTime);
                 if (timeSinceLastStartOrFinish < checkConfiguration.gracePeriod) {
-                    return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ true,
+                    return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/
                             "within " + checkConfiguration.gracePeriod + "ms grace period ("
                                     + timeSinceLastStartOrFinish + "ms)");
                 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 64c19ff..574ab05 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6575,6 +6575,22 @@
                 .getKeyguardController().isKeyguardLocked(mDisplayId);
     }
 
+    boolean isKeyguardLockedOrAodShowing() {
+        return isKeyguardLocked() || isAodShowing();
+    }
+
+    /**
+     * @return whether aod is showing for this display
+     */
+    boolean isAodShowing() {
+        final boolean isAodShowing = mRootWindowContainer.mTaskSupervisor
+                .getKeyguardController().isAodShowing(mDisplayId);
+        if (mDisplayId == DEFAULT_DISPLAY && isAodShowing) {
+            return !isKeyguardGoingAway();
+        }
+        return isAodShowing;
+    }
+
     /**
      * @return whether keyguard is going away on this display
      */
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 6d73739..4eeed5e 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -18,6 +18,7 @@
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
@@ -216,6 +217,9 @@
                 } else if (keyguardShowing && !state.mKeyguardShowing) {
                     transition.addFlag(TRANSIT_FLAG_KEYGUARD_APPEARING);
                 }
+                if (mWindowManager.mFlags.mAodTransition && aodShowing && !state.mAodShowing) {
+                    transition.addFlag(TRANSIT_FLAG_AOD_APPEARING);
+                }
             }
         }
         // Update the task snapshot if the screen will not be turned off. To make sure that the
@@ -238,19 +242,28 @@
         state.mAodShowing = aodShowing;
         state.writeEventLog("setKeyguardShown");
 
-        if (keyguardChanged) {
-            // Irrelevant to AOD.
-            state.mKeyguardGoingAway = false;
-            if (keyguardShowing) {
-                state.mDismissalRequested = false;
+        if (keyguardChanged || (mWindowManager.mFlags.mAodTransition && aodChanged)) {
+            if (keyguardChanged) {
+                // Irrelevant to AOD.
+                state.mKeyguardGoingAway = false;
+                if (keyguardShowing) {
+                    state.mDismissalRequested = false;
+                }
             }
             if (goingAwayRemoved
-                    || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))) {
+                    || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))
+                    || (mWindowManager.mFlags.mAodTransition && aodShowing)) {
                 // Keyguard decided to show or stopped going away. Send a transition to animate back
                 // to the locked state before holding the sleep token again
                 if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
-                    dc.requestTransitionAndLegacyPrepare(
-                            TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null);
+                    if (keyguardChanged) {
+                        dc.requestTransitionAndLegacyPrepare(TRANSIT_TO_FRONT,
+                                TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null);
+                    }
+                    if (mWindowManager.mFlags.mAodTransition && aodChanged && aodShowing) {
+                        dc.requestTransitionAndLegacyPrepare(TRANSIT_TO_FRONT,
+                                TRANSIT_FLAG_AOD_APPEARING, /* trigger= */ null);
+                    }
                 }
                 dc.mWallpaperController.adjustWallpaperWindows();
                 dc.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index bbc35a3..fba29d4 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.util.TimeUtils.NANOS_PER_MS;
 import static android.view.Choreographer.CALLBACK_TRAVERSAL;
 import static android.view.Choreographer.getSfInstance;
@@ -27,25 +26,13 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
 import android.hardware.power.Boost;
 import android.os.Handler;
 import android.os.PowerManagerInternal;
-import android.os.Trace;
 import android.util.ArrayMap;
-import android.util.Log;
 import android.view.Choreographer;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-import android.window.ScreenCapture;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -53,9 +40,6 @@
 import com.android.server.AnimationThread;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 
-import java.util.ArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.function.Supplier;
 
 /**
@@ -73,12 +57,6 @@
      */
     private final Object mCancelLock = new Object();
 
-    /**
-     * Lock for synchronizing {@link #mEdgeExtensions} to prevent race conditions when managing
-     * created edge extension surfaces.
-     */
-    private final Object mEdgeExtensionLock = new Object();
-
     @VisibleForTesting
     Choreographer mChoreographer;
 
@@ -91,12 +69,6 @@
     private final PowerManagerInternal mPowerManagerInternal;
     private boolean mApplyScheduled;
 
-    // Executor to perform the edge extension.
-    // With two threads because in practice we will want to extend two surfaces in one animation,
-    // in which case we want to be able to parallelize those two extensions to cut down latency in
-    // starting the animation.
-    private final ExecutorService mEdgeExtensionExecutor = Executors.newFixedThreadPool(2);
-
     @GuardedBy("mLock")
     @VisibleForTesting
     final ArrayMap<SurfaceControl, RunningAnimation> mPendingAnimations = new ArrayMap<>();
@@ -112,11 +84,6 @@
     @GuardedBy("mLock")
     private boolean mAnimationStartDeferred;
 
-    // Mapping animation leashes to a list of edge extension surfaces associated with them
-    @GuardedBy("mEdgeExtensionLock")
-    private final ArrayMap<SurfaceControl, ArrayList<SurfaceControl>> mEdgeExtensions =
-            new ArrayMap<>();
-
     /**
      * There should only ever be one instance of this class. Usual spot for it is with
      * {@link WindowManagerService}
@@ -175,64 +142,9 @@
         synchronized (mLock) {
             final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
                     finishCallback);
-            boolean requiresEdgeExtension = requiresEdgeExtension(a);
-
-            if (requiresEdgeExtension) {
-                final ArrayList<SurfaceControl> extensionSurfaces = new ArrayList<>();
-                synchronized (mEdgeExtensionLock) {
-                    mEdgeExtensions.put(animationLeash, extensionSurfaces);
-                }
-
-                mPreProcessingAnimations.put(animationLeash, runningAnim);
-
-                // We must wait for t to be committed since otherwise the leash doesn't have the
-                // windows we want to screenshot and extend as children.
-                t.addTransactionCommittedListener(mEdgeExtensionExecutor, () -> {
-                    if (!animationLeash.isValid()) {
-                        Log.e(TAG, "Animation leash is not valid");
-                        synchronized (mEdgeExtensionLock) {
-                            mEdgeExtensions.remove(animationLeash);
-                        }
-                        synchronized (mLock) {
-                            mPreProcessingAnimations.remove(animationLeash);
-                        }
-                        return;
-                    }
-                    final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec();
-
-                    final Transaction edgeExtensionCreationTransaction = new Transaction();
-                    edgeExtendWindow(animationLeash,
-                            animationSpec.getRootTaskBounds(), animationSpec.getAnimation(),
-                            edgeExtensionCreationTransaction);
-
-                    synchronized (mLock) {
-                        // only run if animation is not yet canceled by this point
-                        if (mPreProcessingAnimations.get(animationLeash) == runningAnim) {
-                            // In the case the animation is cancelled, edge extensions are removed
-                            // onAnimationLeashLost which is called before onAnimationCancelled.
-                            // So we need to check if the edge extensions have already been removed
-                            // or not, and if so we don't want to apply the transaction.
-                            synchronized (mEdgeExtensionLock) {
-                                if (!mEdgeExtensions.isEmpty()) {
-                                    edgeExtensionCreationTransaction.apply();
-                                }
-                            }
-
-                            mPreProcessingAnimations.remove(animationLeash);
-                            mPendingAnimations.put(animationLeash, runningAnim);
-                            if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
-                                mChoreographer.postFrameCallback(this::startAnimations);
-                            }
-                        }
-                    }
-                });
-            }
-
-            if (!requiresEdgeExtension) {
-                mPendingAnimations.put(animationLeash, runningAnim);
-                if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
-                    mChoreographer.postFrameCallback(this::startAnimations);
-                }
+            mPendingAnimations.put(animationLeash, runningAnim);
+            if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
+                mChoreographer.postFrameCallback(this::startAnimations);
             }
 
             // Some animations (e.g. move animations) require the initial transform to be
@@ -241,10 +153,6 @@
         }
     }
 
-    private boolean requiresEdgeExtension(AnimationSpec a) {
-        return a.asWindowAnimationSpec() != null && a.asWindowAnimationSpec().hasExtension();
-    }
-
     void onAnimationCancelled(SurfaceControl leash) {
         synchronized (mLock) {
             if (mPendingAnimations.containsKey(leash)) {
@@ -374,161 +282,6 @@
         mApplyScheduled = false;
     }
 
-    private void edgeExtendWindow(SurfaceControl leash, Rect bounds, Animation a,
-            Transaction transaction) {
-        final Transformation transformationAtStart = new Transformation();
-        a.getTransformationAt(0, transformationAtStart);
-        final Transformation transformationAtEnd = new Transformation();
-        a.getTransformationAt(1, transformationAtEnd);
-
-        // We want to create an extension surface that is the maximal size and the animation will
-        // take care of cropping any part that overflows.
-        final Insets maxExtensionInsets = Insets.min(
-                transformationAtStart.getInsets(), transformationAtEnd.getInsets());
-
-        final int targetSurfaceHeight = bounds.height();
-        final int targetSurfaceWidth = bounds.width();
-
-        if (maxExtensionInsets.left < 0) {
-            final Rect edgeBounds = new Rect(bounds.left, bounds.top, bounds.left + 1,
-                    bounds.bottom);
-            final Rect extensionRect = new Rect(0, 0,
-                    -maxExtensionInsets.left, targetSurfaceHeight);
-            final int xPos = bounds.left + maxExtensionInsets.left;
-            final int yPos = bounds.top;
-            createExtensionSurface(leash, edgeBounds,
-                    extensionRect, xPos, yPos, "Left Edge Extension", transaction);
-        }
-
-        if (maxExtensionInsets.top < 0) {
-            final Rect edgeBounds = new Rect(bounds.left, bounds.top, targetSurfaceWidth,
-                    bounds.top + 1);
-            final Rect extensionRect = new Rect(0, 0,
-                    targetSurfaceWidth, -maxExtensionInsets.top);
-            final int xPos = bounds.left;
-            final int yPos = bounds.top + maxExtensionInsets.top;
-            createExtensionSurface(leash, edgeBounds,
-                    extensionRect, xPos, yPos, "Top Edge Extension", transaction);
-        }
-
-        if (maxExtensionInsets.right < 0) {
-            final Rect edgeBounds = new Rect(bounds.right - 1, bounds.top, bounds.right,
-                    bounds.bottom);
-            final Rect extensionRect = new Rect(0, 0,
-                    -maxExtensionInsets.right, targetSurfaceHeight);
-            final int xPos = bounds.right;
-            final int yPos = bounds.top;
-            createExtensionSurface(leash, edgeBounds,
-                    extensionRect, xPos, yPos, "Right Edge Extension", transaction);
-        }
-
-        if (maxExtensionInsets.bottom < 0) {
-            final Rect edgeBounds = new Rect(bounds.left, bounds.bottom - 1,
-                    bounds.right, bounds.bottom);
-            final Rect extensionRect = new Rect(0, 0,
-                    targetSurfaceWidth, -maxExtensionInsets.bottom);
-            final int xPos = bounds.left;
-            final int yPos = bounds.bottom;
-            createExtensionSurface(leash, edgeBounds,
-                    extensionRect, xPos, yPos, "Bottom Edge Extension", transaction);
-        }
-    }
-
-    private void createExtensionSurface(SurfaceControl leash, Rect edgeBounds,
-            Rect extensionRect, int xPos, int yPos, String layerName,
-            Transaction startTransaction) {
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createExtensionSurface");
-        doCreateExtensionSurface(leash, edgeBounds, extensionRect, xPos, yPos, layerName,
-                startTransaction);
-        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-    }
-
-    private void doCreateExtensionSurface(SurfaceControl leash, Rect edgeBounds,
-            Rect extensionRect, int xPos, int yPos, String layerName,
-            Transaction startTransaction) {
-        ScreenCapture.LayerCaptureArgs captureArgs =
-                new ScreenCapture.LayerCaptureArgs.Builder(leash /* surfaceToExtend */)
-                        .setSourceCrop(edgeBounds)
-                        .setFrameScale(1)
-                        .setPixelFormat(PixelFormat.RGBA_8888)
-                        .setChildrenOnly(true)
-                        .setAllowProtected(true)
-                        .setCaptureSecureLayers(true)
-                        .build();
-        final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
-                ScreenCapture.captureLayers(captureArgs);
-
-        if (edgeBuffer == null) {
-            // The leash we are trying to screenshot may have been removed by this point, which is
-            // likely the reason for ending up with a null edgeBuffer, in which case we just want to
-            // return and do nothing.
-            Log.e(TAG, "Failed to create edge extension - edge buffer is null");
-            return;
-        }
-
-        final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
-                .setName(layerName)
-                .setHidden(true)
-                .setCallsite("DefaultTransitionHandler#startAnimation")
-                .setOpaque(true)
-                .setBufferSize(extensionRect.width(), extensionRect.height())
-                .build();
-
-        BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
-                android.graphics.Shader.TileMode.CLAMP,
-                android.graphics.Shader.TileMode.CLAMP);
-        final Paint paint = new Paint();
-        paint.setShader(shader);
-
-        final Surface surface = new Surface(edgeExtensionLayer);
-        Canvas c = surface.lockHardwareCanvas();
-        c.drawRect(extensionRect, paint);
-        surface.unlockCanvasAndPost(c);
-        surface.release();
-
-        synchronized (mEdgeExtensionLock) {
-            if (!mEdgeExtensions.containsKey(leash)) {
-                // The animation leash has already been removed, so we don't want to attach the
-                // edgeExtension layer and should immediately remove it instead.
-                startTransaction.remove(edgeExtensionLayer);
-                return;
-            }
-
-            startTransaction.reparent(edgeExtensionLayer, leash);
-            startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
-            startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
-            startTransaction.setVisibility(edgeExtensionLayer, true);
-
-            mEdgeExtensions.get(leash).add(edgeExtensionLayer);
-        }
-    }
-
-    private float getScaleXForExtensionSurface(Rect edgeBounds, Rect extensionRect) {
-        if (edgeBounds.width() == extensionRect.width()) {
-            // Top or bottom edge extension, no need to scale the X axis of the extension surface.
-            return 1;
-        }
-        if (edgeBounds.width() == 1) {
-            // Left or right edge extension, scale the surface to be the extensionRect's width.
-            return extensionRect.width();
-        }
-
-        throw new RuntimeException("Unexpected edgeBounds and extensionRect widths");
-    }
-
-    private float getScaleYForExtensionSurface(Rect edgeBounds, Rect extensionRect) {
-        if (edgeBounds.height() == extensionRect.height()) {
-            // Left or right edge extension, no need to scale the Y axis of the extension surface.
-            return 1;
-        }
-        if (edgeBounds.height() == 1) {
-            // Top or bottom edge extension, scale the surface to be the extensionRect's height.
-            return extensionRect.height();
-        }
-
-        throw new RuntimeException("Unexpected edgeBounds and extensionRect heights");
-    }
-
     private static final class RunningAnimation {
         final AnimationSpec mAnimSpec;
         final SurfaceControl mLeash;
@@ -545,22 +298,6 @@
         }
     }
 
-    protected void onAnimationLeashLost(SurfaceControl animationLeash,
-            Transaction t) {
-        synchronized (mEdgeExtensionLock) {
-            if (!mEdgeExtensions.containsKey(animationLeash)) {
-                return;
-            }
-
-            final ArrayList<SurfaceControl> edgeExtensions = mEdgeExtensions.get(animationLeash);
-            for (int i = 0; i < edgeExtensions.size(); i++) {
-                final SurfaceControl extension = edgeExtensions.get(i);
-                t.remove(extension);
-            }
-            mEdgeExtensions.remove(animationLeash);
-        }
-    }
-
     @VisibleForTesting
     interface AnimatorFactory {
         ValueAnimator makeAnimator();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3db1d50..c78cdaa 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -36,6 +36,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -980,6 +981,10 @@
         return false;
     }
 
+    boolean isInAodAppearTransition() {
+        return (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0;
+    }
+
     /**
      * Specifies configuration change explicitly for the window container, so it can be chosen as
      * transition target. This is usually used with transition mode
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 11c5c93..9b3b445 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -526,6 +526,19 @@
         return false;
     }
 
+    boolean isInAodAppearTransition() {
+        if (mCollectingTransition != null && mCollectingTransition.isInAodAppearTransition()) {
+            return true;
+        }
+        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+            if (mWaitingTransitions.get(i).isInAodAppearTransition()) return true;
+        }
+        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+            if (mPlayingTransitions.get(i).isInAodAppearTransition()) return true;
+        }
+        return false;
+    }
+
     /**
      * @return A pair of the transition and restore-behind target for the given {@param container}.
      * @param container An ancestor of a transient-launch activity
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index a8b9fed..644417e 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -166,6 +166,14 @@
                 mFindResults.setWallpaperTarget(w);
                 return false;
             }
+        } else if (mService.mFlags.mAodTransition
+                && mDisplayContent.isKeyguardLockedOrAodShowing()) {
+            if (mService.mPolicy.isKeyguardHostWindow(w.mAttrs)
+                    && w.mTransitionController.isInAodAppearTransition()) {
+                if (DEBUG_WALLPAPER) Slog.v(TAG, "Found aod transition wallpaper target: " + w);
+                mFindResults.setWallpaperTarget(w);
+                return true;
+            }
         }
 
         final boolean animationWallpaper = animatingContainer != null
@@ -678,7 +686,8 @@
     private WallpaperWindowToken getTokenForTarget(WindowState target) {
         if (target == null) return null;
         WindowState window = mFindResults.getTopWallpaper(
-                target.canShowWhenLocked() && mService.isKeyguardLocked());
+                (target.canShowWhenLocked() && mService.isKeyguardLocked())
+                        || (mService.mFlags.mAodTransition && mDisplayContent.isAodShowing()));
         return window == null ? null : window.mToken.asWallpaperToken();
     }
 
@@ -721,7 +730,9 @@
 
         if (mFindResults.wallpaperTarget == null && mFindResults.useTopWallpaperAsTarget) {
             mFindResults.setWallpaperTarget(
-                    mFindResults.getTopWallpaper(mDisplayContent.isKeyguardLocked()));
+                    mFindResults.getTopWallpaper(mService.mFlags.mAodTransition
+                            ? mDisplayContent.isKeyguardLockedOrAodShowing()
+                            : mDisplayContent.isKeyguardLocked()));
         }
     }
 
@@ -885,11 +896,17 @@
         if (mDisplayContent.mWmService.mFlags.mEnsureWallpaperInTransitions) {
             visibleRequested = mWallpaperTarget != null && mWallpaperTarget.isVisibleRequested();
         }
-        updateWallpaperTokens(visibleRequested, mDisplayContent.isKeyguardLocked());
+        updateWallpaperTokens(visibleRequested,
+                mService.mFlags.mAodTransition
+                        ? mDisplayContent.isKeyguardLockedOrAodShowing()
+                        : mDisplayContent.isKeyguardLocked());
 
         ProtoLog.v(WM_DEBUG_WALLPAPER,
                 "Wallpaper at display %d - visibility: %b, keyguardLocked: %b",
-                mDisplayContent.getDisplayId(), visible, mDisplayContent.isKeyguardLocked());
+                mDisplayContent.getDisplayId(), visible,
+                mService.mFlags.mAodTransition
+                        ? mDisplayContent.isKeyguardLockedOrAodShowing()
+                        : mDisplayContent.isKeyguardLocked());
 
         if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) {
             mLastFrozen = mFindResults.isWallpaperTargetForLetterbox;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1c3510d..e3746f1 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3067,7 +3067,6 @@
     @Override
     public void onAnimationLeashLost(Transaction t) {
         mLastLayer = -1;
-        mWmService.mSurfaceAnimationRunner.onAnimationLeashLost(mAnimationLeash, t);
         mAnimationLeash = null;
         mNeedsZBoost = false;
         reassignLayer(t);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index aee32a0..215d6ca 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3582,14 +3582,7 @@
 
     @GuardedBy("getLockObject()")
     private boolean maybeMigrateMemoryTaggingLocked(String backupId) {
-        if (!Flags.setMtePolicyCoexistence()) {
-            Slog.i(LOG_TAG, "Memory Tagging not migrated because coexistence "
-                    + "support is disabled.");
-            return false;
-        }
         if (mOwners.isMemoryTaggingMigrated()) {
-            // TODO: Remove log after Flags.setMtePolicyCoexistence full rollout.
-            Slog.v(LOG_TAG, "Memory Tagging was previously migrated to policy engine.");
             return false;
         }
 
@@ -16354,7 +16347,7 @@
     private static <V> PolicyDefinition<V> getPolicyDefinitionForIdentifier(
             @NonNull String identifier) {
         Objects.requireNonNull(identifier);
-        if (Flags.setMtePolicyCoexistence() && MEMORY_TAGGING_POLICY.equals(identifier)) {
+        if (MEMORY_TAGGING_POLICY.equals(identifier)) {
             return (PolicyDefinition<V>) PolicyDefinition.MEMORY_TAGGING;
         } else {
             return (PolicyDefinition<V>) getPolicyDefinitionForRestriction(identifier);
@@ -23759,46 +23752,21 @@
             Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         }
 
-        if (Flags.setMtePolicyCoexistence()) {
-            enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
-                    UserHandle.USER_ALL);
-        } else {
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                    || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
+                UserHandle.USER_ALL);
 
         synchronized (getLockObject()) {
-            if (Flags.setMtePolicyCoexistence()) {
-                final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
-                        MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
-                if (flags != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
-                    mDevicePolicyEngine.setGlobalPolicy(
-                            PolicyDefinition.MEMORY_TAGGING,
-                            admin,
-                            new IntegerPolicyValue(flags));
-                } else {
-                    mDevicePolicyEngine.removeGlobalPolicy(
-                            PolicyDefinition.MEMORY_TAGGING,
-                            admin);
-                }
+            final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+                    MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+            if (flags != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+                mDevicePolicyEngine.setGlobalPolicy(
+                        PolicyDefinition.MEMORY_TAGGING,
+                        admin,
+                        new IntegerPolicyValue(flags));
             } else {
-                ActiveAdmin admin =
-                        getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-                if (admin != null) {
-                    final String memtagProperty = "arm64.memtag.bootctl";
-                    if (flags == DevicePolicyManager.MTE_ENABLED) {
-                        mInjector.systemPropertiesSet(memtagProperty, "memtag");
-                    } else if (flags == DevicePolicyManager.MTE_DISABLED) {
-                        mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
-                    } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
-                        if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
-                            mInjector.systemPropertiesSet(memtagProperty, "default");
-                        }
-                    }
-                    admin.mtePolicy = flags;
-                    saveSettingsLocked(caller.getUserId());
-                }
+                mDevicePolicyEngine.removeGlobalPolicy(
+                        PolicyDefinition.MEMORY_TAGGING,
+                        admin);
             }
 
             DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
@@ -23817,10 +23785,6 @@
         Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
                 "Only system services can call setMtePolicyBySystem");
 
-        if (!Flags.setMtePolicyCoexistence()) {
-            throw new UnsupportedOperationException("System can not set MTE policy only");
-        }
-
         EnforcingAdmin admin = EnforcingAdmin.createSystemEnforcingAdmin(systemEntity);
         if (policy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
             mDevicePolicyEngine.setGlobalPolicy(
@@ -23858,31 +23822,16 @@
     @Override
     public int getMtePolicy(String callerPackageName) {
         final CallerIdentity caller = getCallerIdentity(callerPackageName);
-        if (Flags.setMtePolicyCoexistence()) {
-            enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
-                    UserHandle.USER_ALL);
-        } else {
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                    || isProfileOwnerOfOrganizationOwnedDevice(caller)
-                    || isSystemUid(caller));
-        }
+        enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
+                UserHandle.USER_ALL);
 
         synchronized (getLockObject()) {
-            if (Flags.setMtePolicyCoexistence()) {
-                final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
-                        MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
-                final Integer policyFromAdmin = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
-                        PolicyDefinition.MEMORY_TAGGING, admin);
-                return (policyFromAdmin != null ? policyFromAdmin
-                        : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY);
-            } else {
-                ActiveAdmin admin =
-                        getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-                return admin != null
-                        ? admin.mtePolicy
-                        : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
-            }
+            final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+                    MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+            final Integer policyFromAdmin = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+                    PolicyDefinition.MEMORY_TAGGING, admin);
+            return (policyFromAdmin != null ? policyFromAdmin
+                    : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY);
         }
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index caaf096..6dfe08c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -433,10 +433,8 @@
                 out.attributeBoolean(null, ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED,
                         mResetPasswordWithTokenMigrated);
             }
-            if (Flags.setMtePolicyCoexistence()) {
-                out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED,
-                        mMemoryTaggingMigrated);
-            }
+            out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED,
+                    mMemoryTaggingMigrated);
             if (Flags.setKeyguardDisabledFeaturesCoexistence()) {
                 out.attributeBoolean(null, ATTR_SET_KEYGUARD_DISABLED_FEATURES_MIGRATED,
                         mSetKeyguardDisabledFeaturesMigrated);
@@ -514,8 +512,7 @@
                     mResetPasswordWithTokenMigrated = Flags.resetPasswordWithTokenCoexistence()
                             && parser.getAttributeBoolean(null,
                             ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, false);
-                    mMemoryTaggingMigrated = Flags.setMtePolicyCoexistence()
-                            && parser.getAttributeBoolean(null,
+                    mMemoryTaggingMigrated = parser.getAttributeBoolean(null,
                             ATTR_MEMORY_TAGGING_MIGRATED, false);
                     mSetKeyguardDisabledFeaturesMigrated =
                             Flags.setKeyguardDisabledFeaturesCoexistence()
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index f731b50..bb6339c 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -64,6 +64,18 @@
 public class SupervisionService extends ISupervisionManager.Stub {
     private static final String LOG_TAG = "SupervisionService";
 
+    /**
+     * Activity action: Requests user confirmation of supervision credentials.
+     *
+     * <p>Use {@link Activity#startActivityForResult} to launch this activity. The result will be
+     * {@link Activity#RESULT_OK} if credentials are valid.
+     *
+     * <p>If supervision credentials are not configured, this action initiates the setup flow.
+     */
+    @VisibleForTesting
+    static final String ACTION_CONFIRM_SUPERVISION_CREDENTIALS =
+            "android.app.supervision.action.CONFIRM_SUPERVISION_CREDENTIALS";
+
     // TODO(b/362756788): Does this need to be a LockGuard lock?
     private final Object mLockDoNoUseDirectly = new Object();
 
@@ -81,25 +93,6 @@
     }
 
     /**
-     * Creates an {@link Intent} that can be used with {@link Context#startActivity(Intent)} to
-     * launch the activity to verify supervision credentials.
-     *
-     * <p>A valid {@link Intent} is always returned if supervision is enabled at the time this
-     * method is called, the launched activity still need to perform validity checks as the
-     * supervision state can change when it's launched. A null intent is returned if supervision is
-     * disabled at the time of this method call.
-     *
-     * <p>A result code of {@link android.app.Activity#RESULT_OK} indicates successful verification
-     * of the supervision credentials.
-     */
-    @Override
-    @Nullable
-    public Intent createConfirmSupervisionCredentialsIntent() {
-        // TODO(b/392961554): Implement createAuthenticationIntent API
-        throw new UnsupportedOperationException();
-    }
-
-    /**
      * Returns whether supervision is enabled for the given user.
      *
      * <p>Supervision is automatically enabled when the supervision app becomes the profile owner or
@@ -139,6 +132,31 @@
         }
     }
 
+    /**
+     * Creates an {@link Intent} that can be used with {@link Context#startActivity(Intent)} to
+     * launch the activity to verify supervision credentials.
+     *
+     * <p>A valid {@link Intent} is always returned if supervision is enabled at the time this
+     * method is called, the launched activity still need to perform validity checks as the
+     * supervision state can change when it's launched. A null intent is returned if supervision is
+     * disabled at the time of this method call.
+     *
+     * <p>A result code of {@link android.app.Activity#RESULT_OK} indicates successful verification
+     * of the supervision credentials.
+     */
+    @Override
+    @Nullable
+    public Intent createConfirmSupervisionCredentialsIntent() {
+        // TODO(b/392961554): (1) Return null if supervision is not enabled.
+        // (2) check if PIN exists before return a valid intent.
+        enforceAnyPermission(QUERY_USERS, MANAGE_USERS);
+        final Intent intent = new Intent(ACTION_CONFIRM_SUPERVISION_CREDENTIALS);
+        // explicitly set the package for security
+        intent.setPackage("com.android.settings");
+
+        return intent;
+    }
+
     @Override
     public void onShellCommand(
             @Nullable FileDescriptor in,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java
similarity index 99%
rename from services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
rename to services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java
index 6ad3df1..ac11216 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java
@@ -102,11 +102,12 @@
 import java.io.IOException;
 
 /**
- * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
+ * Run as {@code atest
+ * FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceMockedTest}
  */
-public final class UserManagerServiceTest {
+public final class UserManagerServiceMockedTest {
 
-    private static final String TAG = UserManagerServiceTest.class.getSimpleName();
+    private static final String TAG = UserManagerServiceMockedTest.class.getSimpleName();
 
     /**
      * Id for a simple user (that doesn't have profiles).
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index 17d8882..ea83825 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
@@ -571,6 +571,95 @@
         assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1);
     }
 
+    @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void pauseButton_panelNotHovered_clickNotTriggeredWhenPaused() {
+        injectFakeMouseActionHoverMoveEvent();
+
+        // Pause autoclick and ensure the panel is not hovered.
+        AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+        when(mockAutoclickTypePanel.isPaused()).thenReturn(true);
+        when(mockAutoclickTypePanel.isHovered()).thenReturn(false);
+        mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+        // Send hover move event.
+        MotionEvent hoverMove = MotionEvent.obtain(
+                /* downTime= */ 0,
+                /* eventTime= */ 100,
+                /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+                /* x= */ 30f,
+                /* y= */ 0f,
+                /* metaState= */ 0);
+        hoverMove.setSource(InputDevice.SOURCE_MOUSE);
+        mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+
+        // Verify click is not triggered.
+        assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse();
+        assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void pauseButton_panelHovered_clickTriggeredWhenPaused() {
+        injectFakeMouseActionHoverMoveEvent();
+
+        // Pause autoclick and hover the panel.
+        AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+        when(mockAutoclickTypePanel.isPaused()).thenReturn(true);
+        when(mockAutoclickTypePanel.isHovered()).thenReturn(true);
+        mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+        // Send hover move event.
+        MotionEvent hoverMove = MotionEvent.obtain(
+                /* downTime= */ 0,
+                /* eventTime= */ 100,
+                /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+                /* x= */ 30f,
+                /* y= */ 0f,
+                /* metaState= */ 0);
+        hoverMove.setSource(InputDevice.SOURCE_MOUSE);
+        mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+
+        // Verify click is triggered.
+        assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue();
+        assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void pauseButton_unhoveringCancelsClickWhenPaused() {
+        injectFakeMouseActionHoverMoveEvent();
+
+        // Pause autoclick and hover the panel.
+        AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+        when(mockAutoclickTypePanel.isPaused()).thenReturn(true);
+        when(mockAutoclickTypePanel.isHovered()).thenReturn(true);
+        mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+        // Send hover move event.
+        MotionEvent hoverMove = MotionEvent.obtain(
+                /* downTime= */ 0,
+                /* eventTime= */ 100,
+                /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+                /* x= */ 30f,
+                /* y= */ 0f,
+                /* metaState= */ 0);
+        hoverMove.setSource(InputDevice.SOURCE_MOUSE);
+        mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+
+        // Verify click is triggered.
+        assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue();
+        assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1);
+
+        // Now simulate the pointer being moved outside the panel.
+        when(mockAutoclickTypePanel.isHovered()).thenReturn(false);
+        mController.clickPanelController.onHoverChange(/* hovered= */ false);
+
+        // Verify pending click is canceled.
+        assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse();
+        assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1);
+    }
+
     private void injectFakeMouseActionHoverMoveEvent() {
         MotionEvent event = getFakeMotionHoverMoveEvent();
         event.setSource(InputDevice.SOURCE_MOUSE);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java
new file mode 100644
index 0000000..9e629f7c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2025 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.server.accessibility.autoclick;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.view.MotionEvent;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Test cases for {@link AutoclickLinearLayout}. */
+@RunWith(AndroidTestingRunner.class)
+public class AutoclickLinearLayoutTest {
+    private boolean mHovered;
+
+    private final AutoclickLinearLayout.OnHoverChangedListener mListener =
+            new AutoclickLinearLayout.OnHoverChangedListener() {
+                @Override
+                public void onHoverChanged(boolean hovered) {
+                    mHovered = hovered;
+                }
+            };
+
+    @Rule
+    public TestableContext mTestableContext =
+            new TestableContext(getInstrumentation().getContext());
+    private AutoclickLinearLayout mAutoclickLinearLayout;
+
+    @Before
+    public void setUp() {
+        mAutoclickLinearLayout = new AutoclickLinearLayout(mTestableContext);
+    }
+
+    @Test
+    public void autoclickLinearLayout_hoverChangedListener_setHovered() {
+        mHovered = false;
+        mAutoclickLinearLayout.setOnHoverChangedListener(mListener);
+        mAutoclickLinearLayout.onHoverChanged(/* hovered= */ true);
+        assertThat(mHovered).isTrue();
+    }
+
+    @Test
+    public void autoclickLinearLayout_hoverChangedListener_setNotHovered() {
+        mHovered = true;
+
+        mAutoclickLinearLayout.setOnHoverChangedListener(mListener);
+        mAutoclickLinearLayout.onHoverChanged(/* hovered= */ false);
+        assertThat(mHovered).isFalse();
+    }
+
+    @Test
+    public void autoclickLinearLayout_onInterceptHoverEvent_hovered() {
+        mAutoclickLinearLayout.setHovered(false);
+        mAutoclickLinearLayout.onInterceptHoverEvent(
+                getFakeMotionEvent(MotionEvent.ACTION_HOVER_ENTER));
+        assertThat(mAutoclickLinearLayout.isHovered()).isTrue();
+
+        mAutoclickLinearLayout.setHovered(false);
+        mAutoclickLinearLayout.onInterceptHoverEvent(
+                getFakeMotionEvent(MotionEvent.ACTION_HOVER_MOVE));
+        assertThat(mAutoclickLinearLayout.isHovered()).isTrue();
+    }
+
+    @Test
+    public void autoclickLinearLayout_onInterceptHoverEvent_hoveredExit() {
+        mAutoclickLinearLayout.setHovered(true);
+        mAutoclickLinearLayout.onInterceptHoverEvent(
+                getFakeMotionEvent(MotionEvent.ACTION_HOVER_EXIT));
+        assertThat(mAutoclickLinearLayout.isHovered()).isFalse();
+    }
+
+    private MotionEvent getFakeMotionEvent(int motionEventAction) {
+        return MotionEvent.obtain(
+                /* downTime= */ 0,
+                /* eventTime= */ 0,
+                /* action= */ motionEventAction,
+                /* x= */ 0,
+                /* y= */ 0,
+                /* metaState= */ 0);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
index 9e12340..f7b16c8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
@@ -78,6 +78,7 @@
 
     private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK;
     private boolean mPaused;
+    private boolean mHovered;
 
     private final ClickPanelControllerInterface clickPanelController =
             new ClickPanelControllerInterface() {
@@ -90,6 +91,11 @@
                 public void toggleAutoclickPause(boolean paused) {
                     mPaused = paused;
                 }
+
+                @Override
+                public void onHoverChange(boolean hovered) {
+                    mHovered = hovered;
+                }
             };
 
     @Before
@@ -412,6 +418,33 @@
         upEvent.recycle();
     }
 
+    @Test
+    public void hovered_IsHovered() {
+        AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting();
+
+        assertThat(mAutoclickTypePanel.isHovered()).isFalse();
+        mContext.onInterceptHoverEvent(getFakeMotionHoverMoveEvent());
+        assertThat(mAutoclickTypePanel.isHovered()).isTrue();
+    }
+
+    @Test
+    public void hovered_OnHoverChange_isHovered() {
+        AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting();
+
+        mHovered = false;
+        mContext.onHoverChanged(true);
+        assertThat(mHovered).isTrue();
+    }
+
+    @Test
+    public void hovered_OnHoverChange_isNotHovered() {
+        AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting();
+
+        mHovered = true;
+        mContext.onHoverChanged(false);
+        assertThat(mHovered).isFalse();
+    }
+
     private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) {
         GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground();
         assertThat(gradientDrawable.getColor().getDefaultColor())
@@ -426,4 +459,14 @@
         assertThat(params.x).isEqualTo(expectedPosition[2]);
         assertThat(params.y).isEqualTo(expectedPosition[3]);
     }
+
+    private MotionEvent getFakeMotionHoverMoveEvent() {
+        return MotionEvent.obtain(
+                /* downTime= */ 0,
+                /* eventTime= */ 0,
+                /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+                /* x= */ 0,
+                /* y= */ 0,
+                /* metaState= */ 0);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
index 8471307..01fee7f 100644
--- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
@@ -226,9 +226,9 @@
         mDiscreteRegistry.recordDiscreteAccess(event2);
     }
 
-    /** This clears in-memory cache and push records into the database. */
     private void flushDiscreteOpsToDatabase() {
-        mDiscreteRegistry.writeAndClearOldAccessHistory();
+        // This clears in-memory cache and push records from cache into the database.
+        mDiscreteRegistry.shutdown();
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
index 21cc3ba..8eea1c7 100644
--- a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
@@ -106,7 +106,8 @@
                     opEvent.getDuration(), opEvent.getAttributionFlags(),
                     (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP);
         }
-        sqlRegistry.writeAndClearOldAccessHistory();
+        // flush records from cache to the database.
+        sqlRegistry.shutdown();
         assertThat(sqlRegistry.getAllDiscreteOps().size()).isEqualTo(RECORD_COUNT);
         assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT);
 
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index da02278..fbf9065 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -39,6 +39,7 @@
 import com.android.server.LocalServices
 import com.android.server.SystemService.TargetUser
 import com.android.server.pm.UserManagerInternal
+import com.android.server.supervision.SupervisionService.ACTION_CONFIRM_SUPERVISION_CREDENTIALS
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -247,6 +248,13 @@
         assertThat(userData.supervisionLockScreenOptions).isNull()
     }
 
+    @Test
+    fun createConfirmSupervisionCredentialsIntent() {
+        val intent = checkNotNull(service.createConfirmSupervisionCredentialsIntent())
+        assertThat(intent.action).isEqualTo(ACTION_CONFIRM_SUPERVISION_CREDENTIALS)
+        assertThat(intent.getPackage()).isEqualTo("com.android.settings")
+    }
+
     private val systemSupervisionPackage: String
         get() = context.getResources().getString(R.string.config_systemSupervision)
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 4a977be..4eac1d1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -66,6 +66,8 @@
 import android.annotation.SuppressLint;
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.graphics.Color;
 import android.graphics.drawable.AdaptiveIconDrawable;
@@ -620,6 +622,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
     public void testAutoGrouped_singleOngoing_removeOngoingChild() {
         final String pkg = "package";
 
@@ -639,7 +642,7 @@
         }
 
         // remove ongoing
-        mGroupHelper.onNotificationRemoved(notifications.get(0));
+        mGroupHelper.onNotificationRemoved(notifications.get(0), new ArrayList<>(), false);
 
         // Summary is no longer ongoing
         verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -776,7 +779,7 @@
         }
 
         // remove ongoing
-        mGroupHelper.onNotificationRemoved(notifications.get(1));
+        mGroupHelper.onNotificationRemoved(notifications.get(1), new ArrayList<>(), false);
 
         // Summary is still ongoing
         verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -936,7 +939,7 @@
             mGroupHelper.onNotificationPosted(r, false);
         }
 
-        mGroupHelper.onNotificationRemoved(notifications.get(0));
+        mGroupHelper.onNotificationRemoved(notifications.get(0), new ArrayList<>(), false);
 
         // Summary should still be autocancelable
         verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -944,6 +947,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
     @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
     public void testDropToZeroRemoveGroup_disableFlag() {
         final String pkg = "package";
@@ -963,19 +967,20 @@
         Mockito.reset(mCallback);
 
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
-            mGroupHelper.onNotificationRemoved(posted.remove(0));
+            mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
         }
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
         Mockito.reset(mCallback);
 
-        mGroupHelper.onNotificationRemoved(posted.remove(0));
+        mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString(), anyString());
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
     public void testDropToZeroRemoveGroup() {
         final String pkg = "package";
         ArrayList<NotificationRecord> posted = new ArrayList<>();
@@ -994,13 +999,13 @@
         Mockito.reset(mCallback);
 
         for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
-            mGroupHelper.onNotificationRemoved(posted.remove(0));
+            mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
         }
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
         Mockito.reset(mCallback);
 
-        mGroupHelper.onNotificationRemoved(posted.remove(0));
+        mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString(), anyString());
     }
@@ -1072,6 +1077,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
     @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
     public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_alwaysGroup() {
         final String pkg = "package";
@@ -1091,7 +1097,7 @@
         Mockito.reset(mCallback);
 
         for (int i = posted.size() - 2; i >= 0; i--) {
-            mGroupHelper.onNotificationRemoved(posted.remove(i));
+            mGroupHelper.onNotificationRemoved(posted.remove(i), new ArrayList<>(), false);
         }
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
@@ -1114,7 +1120,8 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
     public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
         final String pkg = "package";
         ArrayList<NotificationRecord> posted = new ArrayList<>();
@@ -1134,7 +1141,7 @@
         Mockito.reset(mCallback);
 
         for (int i = posted.size() - 2; i >= 0; i--) {
-            mGroupHelper.onNotificationRemoved(posted.remove(i));
+            mGroupHelper.onNotificationRemoved(posted.remove(i), new ArrayList<>(), false);
         }
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
@@ -1481,7 +1488,7 @@
         }
 
         // Remove last notification (the only one with different icon and color)
-        mGroupHelper.onNotificationRemoved(notifications.get(lastIdx));
+        mGroupHelper.onNotificationRemoved(notifications.get(lastIdx), new ArrayList<>(), false);
 
         // Summary should be updated to the common icon and color
         verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -2162,7 +2169,7 @@
             NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
                     UserHandle.SYSTEM, "testGrp " + i, false);
             r.setOverrideGroupKey(expectedGroupKey);
-            mGroupHelper.onNotificationRemoved(r, notificationList);
+            mGroupHelper.onNotificationRemoved(r, notificationList, false);
         }
         // Check that the autogroup summary is removed
         verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
@@ -2206,7 +2213,7 @@
         Mockito.reset(mCallback);
         for (NotificationRecord r: childrenToRemove) {
             notificationList.remove(r);
-            mGroupHelper.onNotificationRemoved(r, notificationList);
+            mGroupHelper.onNotificationRemoved(r, notificationList, false);
         }
         // Only call onGroupedNotificationRemovedWithDelay with the summary notification
         mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
@@ -2270,7 +2277,7 @@
         Mockito.reset(mCallback);
         for (NotificationRecord r: childrenToRemove) {
             notificationList.remove(r);
-            mGroupHelper.onNotificationRemoved(r, notificationList);
+            mGroupHelper.onNotificationRemoved(r, notificationList, false);
         }
         // Only call onGroupedNotificationRemovedWithDelay with the summary notification
         mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
@@ -2327,7 +2334,7 @@
         for (NotificationRecord r: notificationList) {
             if (r.getGroupKey().contains(groupToRemove)) {
                 r.isCanceled = true;
-                mGroupHelper.onNotificationRemoved(r, notificationList);
+                mGroupHelper.onNotificationRemoved(r, notificationList, false);
             }
         }
         // Only call onGroupedNotificationRemovedWithDelay with the summary notification
@@ -2452,7 +2459,7 @@
                 true);
         assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
         notificationList.remove(notifToInvalidate);
-        mGroupHelper.onNotificationRemoved(notifToInvalidate, notificationList);
+        mGroupHelper.onNotificationRemoved(notifToInvalidate, notificationList, false);
 
         // Check that the autogroup was updated
         verify(mCallback, never()).removeAutoGroup(anyString());
@@ -4023,7 +4030,7 @@
         //Cancel child 0 => remove cached summary
         childToRemove.isCanceled = true;
         notificationListAfterGrouping.remove(childToRemove);
-        mGroupHelper.onNotificationRemoved(childToRemove, notificationListAfterGrouping);
+        mGroupHelper.onNotificationRemoved(childToRemove, notificationListAfterGrouping, false);
         CachedSummary cachedSummary = mGroupHelper.findCanceledSummary(pkg, String.valueOf(id), id,
                 UserHandle.SYSTEM.getIdentifier());
         assertThat(cachedSummary).isNull();
@@ -4399,4 +4406,41 @@
                 "PeopleSection(priority)");
     }
 
+    @Test
+    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+    public void onNotificationRemoved_lastChildOfCachedSummary_firesCachedSummaryDeleteIntent() {
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+        final String pkg = "package";
+        NotificationRecord onlyChildOfFirstGroup = null;
+        PendingIntent deleteIntentofFirstSummary = PendingIntent.getActivity(mContext, 1,
+                new Intent(), PendingIntent.FLAG_IMMUTABLE);
+        // Post singleton groups, above forced group limit, so they are force grouped
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            NotificationRecord summary = getNotificationRecord(pkg, i,
+                    String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
+            notificationList.add(summary);
+            NotificationRecord child = getNotificationRecord(pkg, i + 42,
+                    String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + i, false);
+            notificationList.add(child);
+            summaryByGroup.put(summary.getGroupKey(), summary);
+            if (i == 0) {
+                onlyChildOfFirstGroup = child;
+                summary.getNotification().deleteIntent = deleteIntentofFirstSummary;
+            }
+            mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+            summary.isCanceled = true;  // simulate removing the app summary
+            mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+        }
+        // Sparse group autogrouping would've removed the summary.
+        notificationList.remove(0);
+
+        // Now remove the only child of the first (force-grouped, cuz sparse) group.
+        notificationList.remove(0);
+        onlyChildOfFirstGroup.isCanceled = true;
+        mGroupHelper.onNotificationRemoved(onlyChildOfFirstGroup, notificationList, true);
+
+        verify(mCallback).sendAppProvidedSummaryDeleteIntent(eq(pkg),
+                eq(deleteIntentofFirstSummary));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 37ab541..3727bbe 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -349,6 +349,7 @@
 
 import com.google.android.collect.Lists;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -2788,8 +2789,8 @@
                 nr1.getSbn().getId(), nr1.getSbn().getUserId());
         waitForIdle();
 
-        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any());
-        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any());
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any(), eq(false));
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any(), eq(false));
 
         // GroupHelper would send 'remove summary' event
         mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName(),
@@ -3155,8 +3156,8 @@
         waitForIdle();
 
         // Check that onGroupedNotificationRemovedWithDelay was called only once
-        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any());
-        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any());
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any(), eq(false));
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any(), eq(false));
         verify(mGroupHelper, times(1)).onGroupedNotificationRemovedWithDelay(eq(summary), any(),
                 any());
     }
@@ -3201,9 +3202,9 @@
         waitForIdle();
 
         // Check that onGroupedNotificationRemovedWithDelay was never called: summary was canceled
-        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any());
-        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any());
-        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(summary), any());
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any(), eq(false));
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any(), eq(false));
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(summary), any(), eq(false));
         verify(mGroupHelper, never()).onGroupedNotificationRemovedWithDelay(any(), any(), any());
     }
 
@@ -14122,9 +14123,10 @@
         waitForIdle();
 
         // Check that child notifications are also removed
-        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary), any());
-        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any());
-        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any());
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary), any(),
+                eq(false));
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any(), eq(false));
+        verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any(), eq(false));
 
         // Make sure the summary was removed and not re-posted
         assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
@@ -18659,4 +18661,35 @@
         }
     }
 
+    @Test
+    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+    public void clearAll_fromUser_willSendDeleteIntentForCachedSummaries() throws Exception {
+        NotificationRecord n = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag",
+                n.getSbn().getId(), n.getSbn().getNotification(), n.getSbn().getUserId());
+        waitForIdle();
+        n = Iterables.getOnlyElement(mService.mNotificationList);
+
+        mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), n.getUserId());
+        waitForIdle();
+
+        verify(mGroupHelper).onNotificationRemoved(eq(n), any(), eq(true));
+    }
+
+    @Test
+    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+    public void cancel_fromApp_willNotSendDeleteIntentForCachedSummaries() throws Exception {
+        NotificationRecord n = generateNotificationRecord(
+                mTestNotificationChannel, 1, "group", true);
+        mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag",
+                n.getSbn().getId(), n.getSbn().getNotification(), n.getSbn().getUserId());
+        waitForIdle();
+        n = Iterables.getOnlyElement(mService.mNotificationList);
+
+        mBinderService.cancelAllNotifications(mPkg, mUserId);
+        waitForIdle();
+
+        verify(mGroupHelper).onNotificationRemoved(eq(n), any(), eq(false));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 5dea44d..752910d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -1219,7 +1219,8 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(
                 PKG_N_MR1, UID_N_MR1, false, USER_SYSTEM);
-        String expected = "<ranking version=\"4\">\n"
+        String expected = "<ranking version=\"4\" "
+                + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
                 + "<package name=\"com.example.o\" show_badge=\"true\" "
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
                 + "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
@@ -1303,7 +1304,8 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(
                 PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
-        String expected = "<ranking version=\"4\">\n"
+        String expected = "<ranking version=\"4\" "
+                + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
                 // Importance 0 because off in permissionhelper
                 + "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1389,7 +1391,8 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(
                 PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
-        String expected = "<ranking version=\"4\">\n"
+        String expected = "<ranking version=\"4\" "
+                + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
                 // Importance 0 because off in permissionhelper
                 + "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1440,7 +1443,8 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(
                 PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
-        String expected = "<ranking version=\"4\">\n"
+        String expected = "<ranking version=\"4\" "
+                + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
                 // Packages that exist solely in permissionhelper
                 + "<package name=\"" + PKG_P + "\" importance=\"3\" />\n"
                 + "<package name=\"" + PKG_O + "\" importance=\"0\" />\n"
@@ -4474,7 +4478,7 @@
     }
 
     @Test
-    public void testBubblePreference_upgradeWithSAWPermission() throws Exception {
+    public void testBubblePreference_noLastVersionWithSAWPermission() throws Exception {
         when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
                 anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
 
@@ -4495,6 +4499,51 @@
     }
 
     @Test
+    public void testBubblePreference_differentLastVersionWithSAWPermission() throws Exception {
+        when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
+                anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
+        final String xml = "<ranking version=\"4\" last_bubbles_version_upgrade=\"34\">\n"
+                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n"
+                + "<channel id=\"someId\" name=\"hi\""
+                + " importance=\"3\"/>"
+                + "</package>"
+                + "</ranking>";
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        assertEquals(BUBBLE_PREFERENCE_ALL, mHelper.getBubblePreference(PKG_O, UID_O));
+        assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
+    }
+
+    @Test
+    public void testBubblePreference_sameLastVersionWithSAWPermission() throws Exception {
+        when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
+                anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
+
+        final String xml = "<ranking version=\"4\" "
+                + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
+                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n"
+                + "<channel id=\"someId\" name=\"hi\""
+                + " importance=\"3\"/>"
+                + "</package>"
+                + "</ranking>";
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        assertEquals(DEFAULT_BUBBLE_PREFERENCE, mHelper.getBubblePreference(PKG_O, UID_O));
+        assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
+        // Version was the same SAW check should not have happened
+        verify(mAppOpsManager, never()).noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
+                anyString(), eq(null), anyString());
+    }
+
+    @Test
     public void testBubblePreference_upgradeWithSAWThenUserOverride() throws Exception {
         when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
                 anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 9d4fe9e..39a849a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -32,6 +32,7 @@
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
@@ -133,6 +134,7 @@
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
@@ -736,6 +738,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    @DisableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
     public void testOrientation_dontAllowFixedOrientationForCameraCompatFreeformIfNotEnabled() {
         final ActivityRecord activity = setupDisplayAndActivityForCameraCompat(
                 /* isCameraRunning= */ true, WINDOWING_MODE_FREEFORM);
@@ -749,6 +753,22 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+            Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION})
+    public void testOrientation_dontAllowFixedOrientationForCameraCompatFreeformIfOptedOut() {
+        final ActivityRecord activity = setupDisplayAndActivityForCameraCompat(
+                /* isCameraRunning= */ true, WINDOWING_MODE_FREEFORM);
+
+        // Task in landscape.
+        assertEquals(ORIENTATION_LANDSCAPE, activity.getTask().getConfiguration().orientation);
+        // Activity is not letterboxed.
+        assertEquals(ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation);
+        assertFalse(activity.mAppCompatController.getAspectRatioPolicy()
+                .isLetterboxedForFixedOrientationAndAspectRatio());
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testOrientation_noFixedOrientationForCameraCompatFreeformIfCameraNotRunning() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
index c934c55..e3a8776 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
@@ -486,7 +486,7 @@
 
         // setup state
         when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
-                new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
+                new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed"));
         when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
 
         // prepare call
@@ -523,7 +523,7 @@
                 mCallerApp);
         when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
         when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
-                new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
+                new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed"));
 
         // prepare call
         PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
@@ -572,7 +572,7 @@
         when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
                 BalVerdict.BLOCK);
         when(otherProcess.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
-                new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
+                new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed"));
 
         // prepare call
         PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java
index 99e730a..cd5f391 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java
@@ -92,7 +92,7 @@
     @Test
     public void intent_visible_noLog() {
         useIntent();
-        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "visible");
+        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "visible");
         mState.setResultForCaller(finalVerdict);
         mState.setResultForRealCaller(BalVerdict.BLOCK);
         assertThat(mController.shouldLogStats(finalVerdict, mState)).isFalse();
@@ -101,7 +101,7 @@
     @Test
     public void intent_saw_log() {
         useIntent();
-        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW");
+        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW");
         mState.setResultForCaller(finalVerdict);
         mState.setResultForRealCaller(BalVerdict.BLOCK);
         assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue();
@@ -111,7 +111,7 @@
     @Test
     public void pendingIntent_callerOnly_saw_log() {
         usePendingIntent();
-        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW");
+        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW");
         mState.setResultForCaller(finalVerdict);
         mState.setResultForRealCaller(BalVerdict.BLOCK);
         assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue();
@@ -121,7 +121,7 @@
     @Test
     public void pendingIntent_realCallerOnly_saw_log() {
         usePendingIntent();
-        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW")
+        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW")
                 .setBasedOnRealCaller();
         mState.setResultForCaller(BalVerdict.BLOCK);
         mState.setResultForRealCaller(finalVerdict);
@@ -131,7 +131,7 @@
 
     @Test
     public void intent_shouldLogIntentActivity() {
-        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW");
+        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW");
         useIntent(APP1_UID);
         assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse();
         useIntent(SYSTEM_UID);
@@ -140,7 +140,7 @@
 
     @Test
     public void pendingIntent_shouldLogIntentActivityForCaller() {
-        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW");
+        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW");
         usePendingIntent(APP1_UID, APP2_UID);
         assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse();
         usePendingIntent(SYSTEM_UID, SYSTEM_UID);
@@ -153,7 +153,7 @@
 
     @Test
     public void pendingIntent_shouldLogIntentActivityForRealCaller() {
-        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false,
+        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
                 "SAW").setBasedOnRealCaller();
         usePendingIntent(APP1_UID, APP2_UID);
         assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse();
@@ -168,7 +168,7 @@
     @Test
     public void pendingIntent_realCallerOnly_visible_noLog() {
         usePendingIntent();
-        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
                 "visible").setBasedOnRealCaller();
         mState.setResultForCaller(BalVerdict.BLOCK);
         mState.setResultForRealCaller(finalVerdict);
@@ -178,7 +178,7 @@
     @Test
     public void pendingIntent_callerOnly_visible_noLog() {
         usePendingIntent();
-        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "visible");
+        BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "visible");
         mState.setResultForCaller(finalVerdict);
         mState.setResultForRealCaller(BalVerdict.BLOCK);
         assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue();
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index 51706d7..fe9a6e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -305,7 +305,7 @@
     @Test
     public void testRegularActivityStart_allowedByCaller_isAllowed() {
         // setup state
-        BalVerdict callerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+        BalVerdict callerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
                 "CallerIsVisible");
         mController.setCallerVerdict(callerVerdict);
         mController.setRealCallerVerdict(BalVerdict.BLOCK);
@@ -340,7 +340,7 @@
     @Test
     public void testRegularActivityStart_allowedByRealCaller_isAllowed() {
         // setup state
-        BalVerdict realCallerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+        BalVerdict realCallerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
                 "RealCallerIsVisible");
         mController.setCallerVerdict(BalVerdict.BLOCK);
         mController.setRealCallerVerdict(realCallerVerdict);
@@ -373,9 +373,9 @@
     public void testRegularActivityStart_allowedByCallerAndRealCaller_returnsCallerVerdict() {
         // setup state
         BalVerdict callerVerdict =
-                new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerHasPermission");
+                new BalVerdict(BAL_ALLOW_PERMISSION, "CallerHasPermission");
         BalVerdict realCallerVerdict =
-                new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+                new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible");
         mController.setCallerVerdict(callerVerdict);
         mController.setRealCallerVerdict(realCallerVerdict);
 
@@ -411,9 +411,9 @@
     public void testPendingIntent_allowedByCallerAndRealCallerButOptOut_isBlocked() {
         // setup state
         BalVerdict callerVerdict =
-                new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerhasPermission");
+                new BalVerdict(BAL_ALLOW_PERMISSION, "CallerhasPermission");
         BalVerdict realCallerVerdict =
-                new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+                new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible");
         mController.setCallerVerdict(callerVerdict);
         mController.setRealCallerVerdict(realCallerVerdict);
 
@@ -452,7 +452,7 @@
     public void testPendingIntent_allowedByCallerAndOptIn_isAllowed() {
         // setup state
         BalVerdict callerVerdict =
-                new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "CallerIsVisible");
+                new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "CallerIsVisible");
         mController.setCallerVerdict(callerVerdict);
         mController.setRealCallerVerdict(BalVerdict.BLOCK);
 
@@ -489,7 +489,7 @@
     public void testPendingIntent_allowedByRealCallerAndOptIn_isAllowed() {
         // setup state
         BalVerdict realCallerVerdict =
-                new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+                new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible");
         mController.setCallerVerdict(BalVerdict.BLOCK);
         mController.setRealCallerVerdict(realCallerVerdict);
 
@@ -571,7 +571,6 @@
                         + "callerApp: mCallerApp; "
                         + "inVisibleTask: false; "
                         + "balAllowedByPiCreator: BSP.ALLOW_BAL; "
-                        + "balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
                         + "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
                         + "hasRealCaller: true; "
                         + "isCallForResult: false; "
@@ -674,7 +673,6 @@
                         + "callerApp: mCallerApp; "
                         + "inVisibleTask: false; "
                         + "balAllowedByPiCreator: BSP.NONE; "
-                        + "balAllowedByPiCreatorWithHardening: BSP.NONE; "
                         + "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
                         + "hasRealCaller: true; "
                         + "isCallForResult: false; "
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index 50876c7..f5bec04 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -192,7 +192,8 @@
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
-    public void testIsFreeformLetterboxingForCameraAllowed_overrideDisabled_returnsFalse() {
+    @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
+    public void testIsFreeformLetterboxingForCameraAllowed_optInMechanism_notOptedIn_retFalse() {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
         onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -201,6 +202,17 @@
     }
 
     @Test
+    @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+            FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+    public void testIsFreeformLetterboxingForCameraAllowed_notOptedOut_returnsTrue() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertTrue(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
+    }
+
+    @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testIsFreeformLetterboxingForCameraAllowed_cameraNotRunning_returnsFalse() {
@@ -222,6 +234,7 @@
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testIsFreeformLetterboxingForCameraAllowed_optInFreeformCameraRunning_true() {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -346,6 +359,7 @@
 
     @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
     public void testShouldApplyCameraCompatFreeformTreatment_overrideNotEnabled_returnsFalse() {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
@@ -356,6 +370,18 @@
     }
 
     @Test
+    @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+            FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+    public void testShouldApplyCameraCompatFreeformTreatment_notOptedOut_returnsTrue() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
+                /* checkOrientation */ true));
+    }
+
+    @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)
     public void testShouldApplyCameraCompatFreeformTreatment_enabledByOverride_returnsTrue() {
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 1f0bd61..544f94b 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -15,6 +15,7 @@
         "modules-utils-testable-device-config-defaults",
     ],
     srcs: [
+        "src/**/*.aidl",
         "src/**/*.java",
         "src/**/*.kt",
     ],
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index eac4267..7ec8f9c 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -76,7 +76,7 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoInteractions
 import org.mockito.Mockito.`when`
 import org.mockito.stubbing.OngoingStubbing
 
@@ -209,7 +209,7 @@
 
     @Test
     fun testStart() {
-        verifyZeroInteractions(native)
+        verifyNoInteractions(native)
 
         service.start()
         verify(native).start()
@@ -217,7 +217,7 @@
 
     @Test
     fun testInputSettingsUpdatedOnSystemRunning() {
-        verifyZeroInteractions(native)
+        verifyNoInteractions(native)
 
         runWithShellPermissionIdentity {
             service.systemRunning()
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 73192ea..f8cb86b 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -15,32 +15,37 @@
  */
 package com.android.test.input
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.filters.MediumTest
-
 import android.app.ActivityManager
 import android.app.ApplicationExitInfo
-import android.content.Context
-import android.graphics.Rect
+import android.app.Instrumentation
+import android.content.Intent
 import android.hardware.display.DisplayManager
 import android.os.Build
+import android.os.Bundle
+import android.os.IBinder
 import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS
 import android.os.SystemClock
-import android.server.wm.CtsWindowInfoUtils.waitForStableWindowGeometry
+import android.server.wm.CtsWindowInfoUtils.getWindowCenter
+import android.server.wm.CtsWindowInfoUtils.waitForWindowOnTop
 import android.testing.PollingCheck
-
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiDevice
 import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
-
+import com.android.cts.input.BlockingQueueEventVerifier
 import com.android.cts.input.DebugInputRule
 import com.android.cts.input.ShowErrorDialogsRule
 import com.android.cts.input.UinputTouchScreen
-
+import com.android.cts.input.inputeventmatchers.withMotionAction
 import java.time.Duration
-
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.function.Supplier
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
@@ -51,13 +56,34 @@
 import org.junit.runner.RunWith
 
 /**
+ * Click on the center of the window identified by the provided window token.
+ * The click is performed using "UinputTouchScreen" device.
+ * If the touchscreen device is closed too soon, it may cause the click to be dropped. Therefore,
+ * the provided runnable can ensure that the click is delivered before the device is closed, thus
+ * avoiding this race.
+ */
+private fun clickOnWindow(
+    token: IBinder,
+    displayId: Int,
+    instrumentation: Instrumentation,
+    waitForEvent: Runnable,
+) {
+    val displayManager = instrumentation.context.getSystemService(DisplayManager::class.java)
+    val display = displayManager.getDisplay(displayId)
+    val point = getWindowCenter({ token }, display.displayId)
+    UinputTouchScreen(instrumentation, display).use { touchScreen ->
+        touchScreen.touchDown(point.x, point.y).lift()
+        // If the device is allowed to close without waiting here, the injected click may be dropped
+        waitForEvent.run()
+    }
+}
+
+/**
  * This test makes sure that an unresponsive gesture monitor gets an ANR.
  *
  * The gesture monitor must be registered from a different process than the instrumented process.
- * Otherwise, when the test runs, you will get:
- * Test failed to run to completion.
- * Reason: 'Instrumentation run failed due to 'keyDispatchingTimedOut''.
- * Check device logcat for details
+ * Otherwise, when the test runs, you will get: Test failed to run to completion. Reason:
+ * 'Instrumentation run failed due to 'keyDispatchingTimedOut''. Check device logcat for details
  * RUNNER ERROR: Instrumentation run failed due to 'keyDispatchingTimedOut'
  */
 @MediumTest
@@ -65,30 +91,43 @@
 class AnrTest {
     companion object {
         private const val TAG = "AnrTest"
-        private const val ALL_PIDS = 0
         private const val NO_MAX = 0
     }
 
     private val instrumentation = InstrumentationRegistry.getInstrumentation()
-    private var hideErrorDialogs = 0
     private lateinit var PACKAGE_NAME: String
-    private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
-            Build.HW_TIMEOUT_MULTIPLIER)
+    private val DISPATCHING_TIMEOUT =
+        (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * Build.HW_TIMEOUT_MULTIPLIER)
+    private var remoteWindowToken: IBinder? = null
+    private var remoteDisplayId: Int? = null
+    private var remotePid: Int? = null
+    private val remoteInputEvents = LinkedBlockingQueue<InputEvent>()
+    private val verifier = BlockingQueueEventVerifier(remoteInputEvents)
 
-    @get:Rule
-    val debugInputRule = DebugInputRule()
+    val binder =
+        object : IAnrTestService.Stub() {
+            override fun provideActivityInfo(token: IBinder, displayId: Int, pid: Int) {
+                remoteWindowToken = token
+                remoteDisplayId = displayId
+                remotePid = pid
+            }
 
-    @get:Rule
-    val showErrorDialogs = ShowErrorDialogsRule()
+            override fun notifyMotion(event: MotionEvent) {
+                remoteInputEvents.add(event)
+            }
+        }
+
+    @get:Rule val showErrorDialogs = ShowErrorDialogsRule()
+
+    @get:Rule val debugInputRule = DebugInputRule()
 
     @Before
     fun setUp() {
+        startUnresponsiveActivity()
         PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName()
     }
 
-    @After
-    fun tearDown() {
-    }
+    @After fun tearDown() {}
 
     @Test
     @DebugInputRule.DebugInput(bug = 339924248)
@@ -112,7 +151,7 @@
         val timestamp = System.currentTimeMillis()
         val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
         val closeAppButton: UiObject2? =
-                uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
+            uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
         if (closeAppButton == null) {
             fail("Could not find anr dialog/close button")
             return
@@ -120,10 +159,10 @@
         closeAppButton.click()
         /**
          * We must wait for the app to be fully closed before exiting this test. This is because
-         * another test may again invoke 'am start' for the same activity.
-         * If the 1st process that got ANRd isn't killed by the time second 'am start' runs,
-         * the killing logic will apply to the newly launched 'am start' instance, and the second
-         * test will fail because the unresponsive activity will never be launched.
+         * another test may again invoke 'am start' for the same activity. If the 1st process that
+         * got ANRd isn't killed by the time second 'am start' runs, the killing logic will apply to
+         * the newly launched 'am start' instance, and the second test will fail because the
+         * unresponsive activity will never be launched.
          */
         waitForNewExitReasonAfter(timestamp)
     }
@@ -132,7 +171,7 @@
         // Find anr dialog and tap on wait
         val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
         val waitButton: UiObject2? =
-                uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
+            uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
         if (waitButton == null) {
             fail("Could not find anr dialog/wait button")
             return
@@ -144,7 +183,7 @@
         lateinit var infos: List<ApplicationExitInfo>
         instrumentation.runOnMainSync {
             val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)!!
-            infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX)
+            infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, remotePid!!, NO_MAX)
         }
         return infos
     }
@@ -159,37 +198,32 @@
         assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason)
     }
 
-    private fun clickOnObject(obj: UiObject2) {
-        val displayManager =
-            instrumentation.context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
-        val display = displayManager.getDisplay(obj.getDisplayId())
-        val rect: Rect = obj.visibleBounds
-        UinputTouchScreen(instrumentation, display).use { touchScreen ->
-            touchScreen
-                .touchDown(rect.centerX(), rect.centerY())
-                .lift()
-        }
-    }
-
     private fun triggerAnr() {
-        startUnresponsiveActivity()
-        val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
-        val obj: UiObject2? = uiDevice.wait(Until.findObject(By.pkg(PACKAGE_NAME)), 10000)
-
-        if (obj == null) {
-            fail("Could not find unresponsive activity")
-            return
-        }
-
-        clickOnObject(obj)
+        clickOnWindow(
+            remoteWindowToken!!,
+            remoteDisplayId!!,
+            instrumentation,
+        ) { verifier.assertReceivedMotion(withMotionAction(ACTION_DOWN)) }
 
         SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors
     }
 
     private fun startUnresponsiveActivity() {
-        val flags = " -W -n "
-        val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
-        instrumentation.uiAutomation.executeShellCommand(startCmd)
-        waitForStableWindowGeometry(Duration.ofSeconds(5))
+        val intent =
+            Intent(instrumentation.targetContext, UnresponsiveGestureMonitorActivity::class.java)
+        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+        val bundle = Bundle()
+        bundle.putBinder("serviceBinder", binder)
+        intent.putExtra("serviceBundle", bundle)
+        instrumentation.targetContext.startActivity(intent)
+        // first, wait for the token to become valid
+        PollingCheck.check(
+                "UnresponsiveGestureMonitorActivity failed to call 'provideActivityInfo'",
+                Duration.ofSeconds(5).toMillis()) { remoteWindowToken != null }
+        // next, wait for the window of the activity to get on top
+        // we could combine the two checks above, but the current setup makes it easier to detect
+        // errors
+        assertTrue("Remote activity window did not become visible",
+          waitForWindowOnTop(Duration.ofSeconds(5), Supplier { remoteWindowToken }))
     }
 }
diff --git a/tests/Input/src/com/android/test/input/IAnrTestService.aidl b/tests/Input/src/com/android/test/input/IAnrTestService.aidl
new file mode 100644
index 0000000..e3caf06
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/IAnrTestService.aidl
@@ -0,0 +1,17 @@
+package com.android.test.input;
+
+import android.view.MotionEvent;
+
+interface IAnrTestService {
+    /**
+     * Provide the activity information. This includes:
+     * windowToken: the windowToken of the activity window
+     * displayId: the display id on which the activity is positioned
+     * pid: the pid of the activity
+     */
+    void provideActivityInfo(IBinder windowToken, int displayId, int pid);
+    /**
+     * Provide the MotionEvent received by the remote activity.
+     */
+    void notifyMotion(in MotionEvent event);
+}
diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
index 1842f0a..1e44617 100644
--- a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
+++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
@@ -23,20 +23,24 @@
 import android.hardware.input.InputManager
 import android.os.Bundle
 import android.os.Looper
+import android.os.Process
 import android.util.Log
 import android.view.InputChannel
 import android.view.InputEvent
 import android.view.InputEventReceiver
 import android.view.InputMonitor
+import android.view.MotionEvent
 
-class UnresponsiveReceiver(channel: InputChannel, looper: Looper) :
-        InputEventReceiver(channel, looper) {
+class UnresponsiveReceiver(channel: InputChannel, looper: Looper, val service: IAnrTestService) :
+    InputEventReceiver(channel, looper) {
     companion object {
         const val TAG = "UnresponsiveReceiver"
     }
+
     override fun onInputEvent(event: InputEvent) {
         Log.i(TAG, "Received $event")
         // Not calling 'finishInputEvent' in order to trigger the ANR
+        service.notifyMotion(event as MotionEvent)
     }
 }
 
@@ -44,14 +48,27 @@
     companion object {
         const val MONITOR_NAME = "unresponsive gesture monitor"
     }
+
     private lateinit var mInputEventReceiver: InputEventReceiver
     private lateinit var mInputMonitor: InputMonitor
+    private lateinit var service: IAnrTestService
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+        val bundle = intent.getBundleExtra("serviceBundle")!!
+        service = IAnrTestService.Stub.asInterface(bundle.getBinder("serviceBinder"))
         val inputManager = checkNotNull(getSystemService(InputManager::class.java))
         mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId)
-        mInputEventReceiver = UnresponsiveReceiver(
-                mInputMonitor.getInputChannel(), Looper.myLooper()!!)
+        mInputEventReceiver =
+            UnresponsiveReceiver(mInputMonitor.getInputChannel(), Looper.myLooper()!!, service)
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        service.provideActivityInfo(
+            window.decorView.windowToken,
+            display.displayId,
+            Process.myPid(),
+        )
     }
 }
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index a97f9a8..3cccbc4 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -41,6 +41,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -1088,6 +1089,10 @@
 
     @Test
     public void testGetRestrictedTransportsFromCarrierConfig() {
+        assumeTrue(
+                "Configuring restricted transport types is only allowed on a debuggable build",
+                Build.isDebuggable());
+
         final Set<Integer> restrictedTransports = new ArraySet<>();
         restrictedTransports.add(TRANSPORT_CELLULAR);
         restrictedTransports.add(TRANSPORT_WIFI);
@@ -1109,6 +1114,10 @@
 
     @Test
     public void testGetRestrictedTransportsFromCarrierConfig_noRestrictPolicyConfigured() {
+        assumeTrue(
+                "Configuring restricted transport types is only allowed on a debuggable build",
+                Build.isDebuggable());
+
         final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
 
         final PersistableBundleWrapper carrierConfig =
@@ -1123,6 +1132,10 @@
 
     @Test
     public void testGetRestrictedTransportsFromCarrierConfig_noCarrierConfig() {
+        assumeTrue(
+                "Configuring restricted transport types is only allowed on a debuggable build",
+                Build.isDebuggable());
+
         final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
 
         final TelephonySubscriptionSnapshot lastSnapshot =
@@ -1134,6 +1147,10 @@
 
     @Test
     public void testGetRestrictedTransportsFromCarrierConfigAndVcnConfig() {
+        assumeTrue(
+                "Configuring restricted transport types is only allowed on a debuggable build",
+                Build.isDebuggable());
+
         // Configure restricted transport in CarrierConfig
         final Set<Integer> restrictedTransportInCarrierConfig =
                 Collections.singleton(TRANSPORT_WIFI);
diff --git a/tools/codegen/OWNERS b/tools/codegen/OWNERS
index c9bd260..e69de29 100644
--- a/tools/codegen/OWNERS
+++ b/tools/codegen/OWNERS
@@ -1 +0,0 @@
-chiuwinson@google.com