Merge "Split enabledForApp & enabledOnKeyguard settings for FP and Face" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0a61df7..302168d8 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -102,6 +102,7 @@
         "com.android.media.flags.projection-aconfig-java",
         "com.android.net.http.flags-aconfig-exported-java",
         "com.android.net.thread.platform.flags-aconfig-java",
+        "com.android.permission.flags-aconfig-java-export",
         "com.android.ranging.flags.ranging-aconfig-java-export",
         "com.android.server.contextualsearch.flags-java",
         "com.android.server.flags.services-aconfig-java",
@@ -115,6 +116,7 @@
         "framework-jobscheduler-job.flags-aconfig-java",
         "framework_graphics_flags_java_lib",
         "hwui_flags_java_lib",
+        "icu_exported_aconfig_flags_lib",
         "interaction_jank_monitor_flags_lib",
         "keystore2_flags_java-framework",
         "libcore_exported_aconfig_flags_lib",
@@ -163,6 +165,14 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// ICU
+java_aconfig_library {
+    name: "icu_exported_aconfig_flags_lib",
+    aconfig_declarations: "icu_aconfig_flags",
+    mode: "exported",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Camera
 java_aconfig_library {
     name: "camera_platform_flags_core_java_lib",
@@ -1846,6 +1856,41 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// SettingsTheme Lib
+aconfig_declarations {
+    name: "aconfig_settings_theme_flags",
+    package: "com.android.settingslib.widget.theme.flags",
+    container: "system",
+    exportable: true,
+    srcs: [
+        "packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "aconfig_settingstheme_exported_flags_java_lib",
+    aconfig_declarations: "aconfig_settings_theme_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+    mode: "exported",
+    min_sdk_version: "21",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.adservices",
+        "com.android.cellbroadcast",
+        "com.android.devicelock",
+        "com.android.extservices",
+        "com.android.healthfitness",
+        "com.android.mediaprovider",
+        "com.android.permission",
+    ],
+}
+
+java_aconfig_library {
+    name: "aconfig_settingstheme_flags_java_lib",
+    aconfig_declarations: "aconfig_settings_theme_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Quick Access Wallet
 aconfig_declarations {
     name: "android.service.quickaccesswallet.flags-aconfig",
diff --git a/OWNERS b/OWNERS
index 058ea36..aa93a27 100644
--- a/OWNERS
+++ b/OWNERS
@@ -16,6 +16,7 @@
 roosa@google.com #{LAST_RESORT_SUGGESTION}
 smoreland@google.com #{LAST_RESORT_SUGGESTION}
 yamasani@google.com #{LAST_RESORT_SUGGESTION}
+timmurray@google.com #{LAST_RESORT_SUGGESTION}
 
 # API changes are already covered by API-Review+1 (http://mdb/android-api-council)
 # via https://android.git.corp.google.com/All-Projects/+/refs/meta/config/rules.pl.
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6e0defe..15ae79e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3462,7 +3462,7 @@
     method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
     method public void setDevicePolicy(int, int);
     method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public void setDevicePolicy(int, int, int);
-    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public void setDisplayImePolicy(int, int);
+    method public void setDisplayImePolicy(int, int);
     method public void setShowPointerIcon(boolean);
     method public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
     method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public void wakeUp();
@@ -3481,7 +3481,7 @@
     method public int getDevicePolicy(int);
     method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getDimDuration();
     method @Nullable public android.content.ComponentName getHomeComponent();
-    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @Nullable public android.content.ComponentName getInputMethodComponent();
+    method @Nullable public android.content.ComponentName getInputMethodComponent();
     method public int getLockState();
     method @Nullable public String getName();
     method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getScreenOffTimeout();
@@ -3520,7 +3520,7 @@
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
     method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDimDuration(@NonNull java.time.Duration);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
-    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
     method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setScreenOffTimeout(@NonNull java.time.Duration);
@@ -19043,7 +19043,7 @@
 package android.view.inputmethod {
 
   public final class InputMethodInfo implements android.os.Parcelable {
-    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public boolean isVirtualDeviceOnly();
+    method public boolean isVirtualDeviceOnly();
   }
 
   public final class InputMethodManager {
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 1738a92..8fa2362 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -271,6 +271,9 @@
 
     int[] getAllowedAdjustmentKeyTypes();
     void setAssistantAdjustmentKeyTypeState(int type, boolean enabled);
-    int[] getAllowedAdjustmentKeyTypesForPackage(String pkg);
-    void setAssistantAdjustmentKeyTypeStateForPackage(String pkg, int type, boolean enabled);
+    String[] getTypeAdjustmentDeniedPackages();
+    void setTypeAdjustmentForPackageState(String pkg, boolean enabled);
+
+    // TODO: b/389918945 - Remove once nm_binder_perf flags are going to Nextfood.
+    void incrementCounter(String metricId);
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 93d751c..fbe5b94 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -11854,7 +11854,7 @@
                     // If segment limit is exceeded. All segments will be replaced
                     // with a single segment
                     boolean allSameColor = true;
-                    int firstSegmentColor = segments.get(0).getColor();
+                    int firstSegmentColor = segments.getFirst().getColor();
 
                     for (int i = 1; i < segments.size(); i++) {
                         if (segments.get(i).getColor() != firstSegmentColor) {
@@ -11887,8 +11887,31 @@
                     }
                 }
 
+                // If the segments and points can't all fit inside the progress drawable, the
+                // view will replace all segments with a single segment.
+                final int segmentsFallbackColor;
+                if (segments.size() <= 1) {
+                    segmentsFallbackColor = NotificationProgressModel.INVALID_COLOR;
+                } else {
+
+                    boolean allSameColor = true;
+                    int firstSegmentColor = segments.getFirst().getColor();
+                    for (int i = 1; i < segments.size(); i++) {
+                        if (segments.get(i).getColor() != firstSegmentColor) {
+                            allSameColor = false;
+                            break;
+                        }
+                    }
+                    // If the segments are of the same color, the view can just use that color.
+                    // In that case there is no need to send the fallback color.
+                    segmentsFallbackColor = allSameColor ? NotificationProgressModel.INVALID_COLOR
+                            : sanitizeProgressColor(Notification.COLOR_DEFAULT, backgroundColor,
+                                    defaultProgressColor);
+                }
+
                 model = new NotificationProgressModel(segments, points,
-                        Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress);
+                        Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress,
+                        segmentsFallbackColor);
             }
             return model;
         }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 24f2495..1e1ec60 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -665,8 +665,10 @@
 
     private final InstantSource mClock;
     private final RateLimiter mUpdateRateLimiter = new RateLimiter("notify (update)",
+            "notifications.value_client_throttled_notify_update",
             MAX_NOTIFICATION_UPDATE_RATE);
     private final RateLimiter mUnnecessaryCancelRateLimiter = new RateLimiter("cancel (dupe)",
+            "notifications.value_client_throttled_cancel_duplicate",
             MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE);
     // Value is KNOWN_STATUS_ENQUEUED/_CANCELLED
     private final LruCache<NotificationKey, Integer> mKnownNotifications = new LruCache<>(100);
@@ -848,19 +850,21 @@
     /** Helper class to rate-limit Binder calls. */
     private class RateLimiter {
 
-        private static final Duration RATE_LIMITER_LOG_INTERVAL = Duration.ofSeconds(5);
+        private static final Duration RATE_LIMITER_LOG_INTERVAL = Duration.ofSeconds(1);
 
         private final RateEstimator mInputRateEstimator;
         private final RateEstimator mOutputRateEstimator;
         private final String mName;
+        private final String mCounterName;
         private final float mLimitRate;
 
         private Instant mLogSilencedUntil;
 
-        private RateLimiter(String name, float limitRate) {
+        private RateLimiter(String name, String counterName, float limitRate) {
             mInputRateEstimator = new RateEstimator();
             mOutputRateEstimator = new RateEstimator();
             mName = name;
+            mCounterName = counterName;
             mLimitRate = limitRate;
         }
 
@@ -880,6 +884,14 @@
                 return;
             }
 
+            if (Flags.nmBinderPerfLogNmThrottling()) {
+                try {
+                    service().incrementCounter(mCounterName);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Ignoring error while trying to log " + mCounterName, e);
+                }
+            }
+
             long nowMillis = now.toEpochMilli();
             Slog.w(TAG, TextUtils.formatSimple(
                     "Shedding %s of %s, rate limit (%s) exceeded: input %s, output would be %s",
@@ -1644,7 +1656,8 @@
         if (Flags.modesApi() && Flags.modesUi()) {
             PackageManager pm = mContext.getPackageManager();
             return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
-                    && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+                    && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                    && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
         } else {
             return false;
         }
@@ -2163,12 +2176,10 @@
      * @hide
      */
     @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
-    public void setAssistantAdjustmentKeyTypeStateForPackage(@NonNull String pkg,
-                                                             @Adjustment.Types int type,
-                                                             boolean enabled) {
+    public void setTypeAdjustmentForPackageState(@NonNull String pkg, boolean enabled) {
         INotificationManager service = service();
         try {
-            service.setAssistantAdjustmentKeyTypeStateForPackage(pkg, type, enabled);
+            service.setTypeAdjustmentForPackageState(pkg, enabled);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 76705dc..6936ddc 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -376,6 +376,14 @@
     }
 
     /**
+     * Returns the task id.
+     * @hide
+     */
+    public int getTaskId() {
+        return taskId;
+    }
+
+    /**
      * Whether this task is visible.
      */
     public boolean isVisible() {
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index edd17e8..914ca73 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -295,6 +295,16 @@
 }
 
 flag {
+  name: "nm_binder_perf_log_nm_throttling"
+  namespace: "systemui"
+  description: "Log throttled operations (notify, cancel) to statsd. This flag will NOT be pushed past Trunkfood."
+  bug: "389918945"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "no_sbnholder"
   namespace: "systemui"
   description: "removes sbnholder from NLS"
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
index a5b58f9..92241f3 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -34,6 +34,35 @@
     private final Context mContext;
     private final ISupervisionManager mService;
 
+    /**
+     * Activity action: ask the human user to enable supervision for this user. Only the app that
+     * holds the {@code SYSTEM_SUPERVISION} role can launch this intent.
+     *
+     * <p>The intent must be invoked via {@link Activity#startActivityForResult} to receive the
+     * result of whether or not the user approved the action. If approved, the result will be {@link
+     * Activity#RESULT_OK}.
+     *
+     * <p>If supervision is already enabled, the operation will return a failure result.
+     *
+     * @hide
+     */
+    public static final String ACTION_ENABLE_SUPERVISION = "android.app.action.ENABLE_SUPERVISION";
+
+    /**
+     * Activity action: ask the human user to disable supervision for this user. Only the app that
+     * holds the {@code SYSTEM_SUPERVISION} role can launch this intent.
+     *
+     * <p>The intent must be invoked via {@link Activity#startActivityForResult} to receive the
+     * result of whether or not the user approved the action. If approved, the result will be {@link
+     * Activity#RESULT_OK}.
+     *
+     * <p>If supervision is not enabled, the operation will return a failure result.
+     *
+     * @hide
+     */
+    public static final String ACTION_DISABLE_SUPERVISION =
+            "android.app.action.DISABLE_SUPERVISION";
+
     /** @hide */
     @UnsupportedAppUsage
     public SupervisionManager(Context context, ISupervisionManager service) {
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index 4ee3a03..18182b8 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -40,3 +40,11 @@
   description: "Flag to enable the SupervisionAppService"
   bug: "389123070"
 }
+
+flag {
+  name: "enable_supervision_settings_screen"
+  is_exported: true
+  namespace: "supervision"
+  description: "Flag that enables the Supervision settings screen with top-level Android settings entry point"
+  bug: "383404606"
+}
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 67ad459..b54e17b 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1684,10 +1684,14 @@
             private IBinder mIBinder;
 
             ConnectionTask(@NonNull FilterComparison filter) {
-                mContext.bindService(filter.getIntent(),
-                        Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
-                        mHandler::post,
-                        this);
+                try {
+                    mContext.bindService(filter.getIntent(),
+                            Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
+                            mHandler::post,
+                            this);
+                } catch (Exception e) {
+                    Log.e(TAG, "Error connecting to service in connection cache", e);
+                }
             }
 
             @Override
@@ -1737,7 +1741,11 @@
                     handleNext();
                     return;
                 }
-                mContext.unbindService(this);
+                try {
+                    mContext.unbindService(this);
+                } catch (Exception e) {
+                    Log.e(TAG, "Error unbinding the cached connection", e);
+                }
                 mActiveConnections.values().remove(this);
             }
         }
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 2f16115..ceafce2 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -287,8 +287,8 @@
     /**
      * Get the device icon of the associated device. The device icon represents the device type.
      *
-     * @return the device icon, or {@code null} if no device icon has been set for the
-     * associated device.
+     * @return the device icon with size 24dp x 24dp.
+     * If the associated device has no icon set, it returns {@code null}.
      *
      * @see AssociationRequest.Builder#setDeviceIcon(Icon)
      */
@@ -377,6 +377,7 @@
         if (this == o) return true;
         if (!(o instanceof AssociationInfo)) return false;
         final AssociationInfo that = (AssociationInfo) o;
+
         return mId == that.mId
                 && mUserId == that.mUserId
                 && mSelfManaged == that.mSelfManaged
@@ -391,11 +392,17 @@
                 && Objects.equals(mDeviceProfile, that.mDeviceProfile)
                 && Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
                 && mSystemDataSyncFlags == that.mSystemDataSyncFlags
-                && (mDeviceIcon == null ? that.mDeviceIcon == null
-                : mDeviceIcon.sameAs(that.mDeviceIcon))
+                && isSameIcon(mDeviceIcon, that.mDeviceIcon)
                 && Objects.equals(mDeviceId, that.mDeviceId);
     }
 
+    private boolean isSameIcon(Icon iconA, Icon iconB) {
+        // Because we've already rescaled and converted both icons to bitmaps,
+        // we can now directly compare them by bitmap.
+        return (iconA == null && iconB == null)
+                || (iconA != null && iconB != null && iconA.getBitmap().sameAs(iconB.getBitmap()));
+    }
+
     @Override
     public int hashCode() {
         return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
@@ -425,7 +432,7 @@
         dest.writeLong(mTimeApprovedMs);
         dest.writeLong(mLastTimeConnectedMs);
         dest.writeInt(mSystemDataSyncFlags);
-        if (mDeviceIcon != null) {
+        if (Flags.associationDeviceIcon() && mDeviceIcon != null) {
             dest.writeInt(1);
             mDeviceIcon.writeToParcel(dest, flags);
         } else {
@@ -455,7 +462,8 @@
         mTimeApprovedMs = in.readLong();
         mLastTimeConnectedMs = in.readLong();
         mSystemDataSyncFlags = in.readInt();
-        if (in.readInt() == 1) {
+        int deviceIcon = in.readInt();
+        if (Flags.associationDeviceIcon() && deviceIcon == 1) {
             mDeviceIcon = Icon.CREATOR.createFromParcel(in);
         } else {
             mDeviceIcon = null;
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 32cbf32..a098a60 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -385,6 +385,10 @@
     public void setAssociatedDevice(AssociatedDevice associatedDevice) {
         mAssociatedDevice = associatedDevice;
     }
+    /** @hide */
+    public void setDeviceIcon(Icon deviceIcon) {
+        mDeviceIcon = deviceIcon;
+    }
 
     /** @hide */
     @NonNull
@@ -492,9 +496,10 @@
         /**
          * Set the device icon for the self-managed device and to display the icon in the
          * self-managed association dialog.
+         * <p>The given device icon will be resized to 24dp x 24dp.
          *
-         * @throws IllegalArgumentException if the icon is not exactly 24dp by 24dp
-         * or if it is {@link Icon#TYPE_URI} or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}.
+         * @throws IllegalArgumentException if the icon is
+         * {@link Icon#TYPE_URI} or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}.
          * @see #setSelfManaged(boolean)
          */
         @NonNull
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index a96ba11..566e78a 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -23,7 +23,6 @@
 import static android.graphics.drawable.Icon.TYPE_URI;
 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
 
-
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -52,10 +51,10 @@
 import android.content.IntentSender;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
-import android.graphics.drawable.VectorDrawable;
 import android.net.MacAddress;
 import android.os.Binder;
 import android.os.Handler;
@@ -110,6 +109,7 @@
 @RequiresFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
 public final class CompanionDeviceManager {
     private static final String TAG = "CDM_CompanionDeviceManager";
+    private static final int ICON_TARGET_SIZE = 24;
 
     /** @hide */
     @IntDef(prefix = {"RESULT_"}, value = {
@@ -474,10 +474,8 @@
 
         if (Flags.associationDeviceIcon()) {
             final Icon deviceIcon = request.getDeviceIcon();
-
-            if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) {
-                throw new IllegalArgumentException("The size of the device icon must be "
-                        + "24dp x 24dp to ensure proper display");
+            if (deviceIcon != null) {
+                request.setDeviceIcon(scaleIcon(deviceIcon, mContext));
             }
         }
 
@@ -547,10 +545,8 @@
 
         if (Flags.associationDeviceIcon()) {
             final Icon deviceIcon = request.getDeviceIcon();
-
-            if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) {
-                throw new IllegalArgumentException("The size of the device icon must be "
-                        + "24dp x 24dp to ensure proper display");
+            if (deviceIcon != null) {
+                request.setDeviceIcon(scaleIcon(deviceIcon, mContext));
             }
         }
 
@@ -2024,33 +2020,26 @@
         }
     }
 
-    private boolean isValidIcon(Icon icon, Context context) {
+    private Icon scaleIcon(Icon icon, Context context) {
+        if (icon == null) return null;
         if (icon.getType() == TYPE_URI_ADAPTIVE_BITMAP || icon.getType() == TYPE_URI) {
             throw new IllegalArgumentException("The URI based Icon is not supported.");
         }
+
+        Bitmap bitmap;
         Drawable drawable = icon.loadDrawable(context);
-        float density = context.getResources().getDisplayMetrics().density;
-
         if (drawable instanceof BitmapDrawable) {
-            Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
-
-            float widthDp = bitmap.getWidth() / density;
-            float heightDp = bitmap.getHeight() / density;
-
-            if (widthDp != 24 || heightDp != 24) {
-                return false;
-            }
-        } else if (drawable instanceof VectorDrawable) {
-            VectorDrawable vectorDrawable = (VectorDrawable) drawable;
-            float widthDp = vectorDrawable.getIntrinsicWidth() / density;
-            float heightDp = vectorDrawable.getIntrinsicHeight() / density;
-
-            if (widthDp != 24 || heightDp != 24) {
-                return false;
-            }
+            bitmap = Bitmap.createScaledBitmap(
+                    ((BitmapDrawable) drawable).getBitmap(), ICON_TARGET_SIZE, ICON_TARGET_SIZE,
+                    false);
         } else {
-            throw new IllegalArgumentException("The format of the device icon is unsupported.");
+            bitmap = Bitmap.createBitmap(context.getResources().getDisplayMetrics(),
+                    ICON_TARGET_SIZE, ICON_TARGET_SIZE, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(bitmap);
+            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+            drawable.draw(canvas);
         }
-        return true;
+
+        return Icon.createWithBitmap(bitmap);
     }
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 99794d7..252db82 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -1116,11 +1116,8 @@
          * @throws SecurityException if the display is not owned by this device or is not
          *                           {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED trusted}
          */
-        @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
         public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) {
-            if (Flags.vdmCustomIme()) {
-                mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy);
-            }
+            mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy);
         }
 
         /**
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 761e75b..95dee9b 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -440,7 +440,6 @@
      *
      * @see Builder#setInputMethodComponent
      */
-    @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
     @Nullable
     public ComponentName getInputMethodComponent() {
         return mInputMethodComponent;
@@ -945,7 +944,6 @@
          * @attr ref android.R.styleable#InputMethod_isVirtualDeviceOnly
          * @attr ref android.R.styleable#InputMethod_showInInputMethodPicker
          */
-        @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME)
         @NonNull
         public Builder setInputMethodComponent(@Nullable ComponentName inputMethodComponent) {
             mInputMethodComponent = inputMethodComponent;
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 9d11710..8266384 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -85,6 +85,8 @@
     private static final boolean DEBUG = false;
     protected static final String REGISTERED_SERVICES_DIR = "registered_services";
 
+    static final long SERVICE_INFO_CACHES_TIMEOUT_MILLIS = 30000; // 30 seconds
+
     public final Context mContext;
     private final String mInterfaceName;
     private final String mMetaDataName;
@@ -96,8 +98,18 @@
     @GuardedBy("mServicesLock")
     private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
 
-    @GuardedBy("mServicesLock")
-    private final ArrayMap<String, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+    @GuardedBy("mServiceInfoCaches")
+    private final ArrayMap<ComponentName, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+
+    private final Handler mBackgroundHandler;
+
+    private final Runnable mClearServiceInfoCachesRunnable = new Runnable() {
+        public void run() {
+            synchronized (mServiceInfoCaches) {
+                mServiceInfoCaches.clear();
+            }
+        }
+    };
 
     private static class UserServices<V> {
         @GuardedBy("mServicesLock")
@@ -172,9 +184,9 @@
         if (isCore) {
             intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         }
-        Handler handler = BackgroundThread.getHandler();
+        mBackgroundHandler = BackgroundThread.getHandler();
         mContext.registerReceiverAsUser(
-                mPackageReceiver, UserHandle.ALL, intentFilter, null, handler);
+                mPackageReceiver, UserHandle.ALL, intentFilter, null, mBackgroundHandler);
 
         // Register for events related to sdcard installation.
         IntentFilter sdFilter = new IntentFilter();
@@ -183,7 +195,7 @@
         if (isCore) {
             sdFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         }
-        mContext.registerReceiver(mExternalReceiver, sdFilter, null, handler);
+        mContext.registerReceiver(mExternalReceiver, sdFilter, null, mBackgroundHandler);
 
         // Register for user-related events
         IntentFilter userFilter = new IntentFilter();
@@ -191,7 +203,7 @@
         if (isCore) {
             userFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         }
-        mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler);
+        mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, mBackgroundHandler);
     }
 
     private void handlePackageEvent(Intent intent, int userId) {
@@ -328,6 +340,10 @@
         public final ComponentName componentName;
         @UnsupportedAppUsage
         public final int uid;
+        /**
+         * The last update time of the package that contains the service.
+         * It's from {@link PackageInfo#lastUpdateTime}.
+         */
         public final long lastUpdateTime;
 
         /** @hide */
@@ -342,7 +358,8 @@
 
         @Override
         public String toString() {
-            return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
+            return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid
+                    + ", lastUpdateTime " + lastUpdateTime;
         }
     }
 
@@ -496,19 +513,56 @@
 
         final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>();
         final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
+        final PackageManager pm = mContext.getPackageManager();
         for (ResolveInfo resolveInfo : resolveInfos) {
+            // Check if the service has been in the service cache.
+            long lastUpdateTime = -1;
+            final android.content.pm.ServiceInfo si = resolveInfo.serviceInfo;
+            final ComponentName componentName = si.getComponentName();
+            if (Flags.optimizeParsingInRegisteredServicesCache()) {
+                try {
+                    PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
+                            PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+                    lastUpdateTime = packageInfo.lastUpdateTime;
+                } catch (NameNotFoundException | SecurityException e) {
+                    Slog.d(TAG, "Fail to get the PackageInfo in generateServicesMap: " + e);
+                    continue;
+                }
+                ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(componentName,
+                        lastUpdateTime);
+                if (serviceInfo != null) {
+                    serviceInfos.add(serviceInfo);
+                    continue;
+                }
+            }
             try {
-                ServiceInfo<V> info = parseServiceInfo(resolveInfo, userId);
+                ServiceInfo<V> info = parseServiceInfo(resolveInfo, lastUpdateTime);
                 if (info == null) {
                     Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
                     continue;
                 }
                 serviceInfos.add(info);
+                if (Flags.optimizeParsingInRegisteredServicesCache()) {
+                    synchronized (mServiceInfoCaches) {
+                        mServiceInfoCaches.put(componentName, info);
+                    }
+                }
             } catch (XmlPullParserException | IOException e) {
                 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
             }
         }
 
+        if (Flags.optimizeParsingInRegisteredServicesCache()) {
+            synchronized (mServiceInfoCaches) {
+                if (!mServiceInfoCaches.isEmpty()) {
+                    mBackgroundHandler.removeCallbacks(mClearServiceInfoCachesRunnable);
+                    mBackgroundHandler.postDelayed(mClearServiceInfoCachesRunnable,
+                            SERVICE_INFO_CACHES_TIMEOUT_MILLIS);
+                }
+            }
+        }
+
         synchronized (mServicesLock) {
             final UserServices<V> user = findOrCreateUserLocked(userId);
             final boolean firstScan = user.services == null;
@@ -645,32 +699,18 @@
         return false;
     }
 
+    /**
+     * If the service has already existed in the caches, this method will not be called to parse
+     * the service.
+     */
     @VisibleForTesting
-    protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, int userId)
+    protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, long lastUpdateTime)
             throws XmlPullParserException, IOException {
         android.content.pm.ServiceInfo si = service.serviceInfo;
         ComponentName componentName = new ComponentName(si.packageName, si.name);
 
         PackageManager pm = mContext.getPackageManager();
 
-        // Check if the service has been in the service cache.
-        long lastUpdateTime = -1;
-        if (Flags.optimizeParsingInRegisteredServicesCache()) {
-            try {
-                PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
-                        PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
-                lastUpdateTime = packageInfo.lastUpdateTime;
-
-                ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(si, lastUpdateTime);
-                if (serviceInfo != null) {
-                    return serviceInfo;
-                }
-            } catch (NameNotFoundException | SecurityException e) {
-                Slog.d(TAG, "Fail to get the PackageInfo in parseServiceInfo: " + e);
-            }
-        }
-
         XmlResourceParser parser = null;
         try {
             parser = si.loadXmlMetaData(pm, mMetaDataName);
@@ -696,13 +736,7 @@
             if (v == null) {
                 return null;
             }
-            ServiceInfo<V> serviceInfo = new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
-            if (Flags.optimizeParsingInRegisteredServicesCache()) {
-                synchronized (mServicesLock) {
-                    mServiceInfoCaches.put(getServiceCacheKey(si), serviceInfo);
-                }
-            }
-            return serviceInfo;
+            return new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
         } catch (NameNotFoundException e) {
             throw new XmlPullParserException(
                     "Unable to load resources for pacakge " + si.packageName);
@@ -873,26 +907,13 @@
         mContext.unregisterReceiver(mUserRemovedReceiver);
     }
 
-    private static String getServiceCacheKey(@NonNull android.content.pm.ServiceInfo serviceInfo) {
-        StringBuilder sb = new StringBuilder(serviceInfo.packageName);
-        sb.append('-');
-        sb.append(serviceInfo.name);
-        return sb.toString();
-    }
-
-    private ServiceInfo<V> getServiceInfoFromServiceCache(
-            @NonNull android.content.pm.ServiceInfo serviceInfo, long lastUpdateTime) {
-        String serviceCacheKey = getServiceCacheKey(serviceInfo);
-        synchronized (mServicesLock) {
-            ServiceInfo<V> serviceCache = mServiceInfoCaches.get(serviceCacheKey);
-            if (serviceCache == null) {
-                return null;
-            }
-            if (serviceCache.lastUpdateTime == lastUpdateTime) {
+    private ServiceInfo<V> getServiceInfoFromServiceCache(@NonNull ComponentName componentName,
+            long lastUpdateTime) {
+        synchronized (mServiceInfoCaches) {
+            ServiceInfo<V> serviceCache = mServiceInfoCaches.get(componentName);
+            if (serviceCache != null && serviceCache.lastUpdateTime == lastUpdateTime) {
                 return serviceCache;
             }
-            // The service is not latest, remove it from the cache.
-            mServiceInfoCaches.remove(serviceCacheKey);
             return null;
         }
     }
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index b380e25..cd48047 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -385,6 +385,42 @@
     }
 
     /**
+     * Whether touchpad acceleration is enabled or not.
+     *
+     * @param context The application context.
+     *
+     * @hide
+     */
+    public static boolean isTouchpadAccelerationEnabled(@NonNull Context context) {
+        if (!isPointerAccelerationFeatureFlagEnabled()) {
+            return false;
+        }
+
+        return Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.System.TOUCHPAD_ACCELERATION_ENABLED, 1, UserHandle.USER_CURRENT)
+                == 1;
+    }
+
+   /**
+    * Enables or disables touchpad acceleration.
+    *
+    * @param context The application context.
+    * @param enabled Will enable touchpad acceleration if true, disable it if
+    *                false.
+    * @hide
+    */
+    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+    public static void setTouchpadAccelerationEnabled(@NonNull Context context,
+            boolean enabled) {
+        if (!isPointerAccelerationFeatureFlagEnabled()) {
+            return;
+        }
+        Settings.System.putIntForUser(context.getContentResolver(),
+                Settings.System.TOUCHPAD_ACCELERATION_ENABLED, enabled ? 1 : 0,
+                UserHandle.USER_CURRENT);
+    }
+
+    /**
      * Returns true if the feature flag for disabling system gestures on touchpads is enabled.
      *
      * @hide
@@ -835,7 +871,6 @@
                 UserHandle.USER_CURRENT);
     }
 
-
     /**
      * Whether Accessibility bounce keys feature is enabled.
      *
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 9181bd0..953ee08 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -771,6 +771,7 @@
      */
     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
     private IContextHubEndpointDiscoveryCallback createDiscoveryCallback(
+            IContextHubService service,
             Executor executor,
             HubEndpointDiscoveryCallback callback,
             @Nullable String serviceDescriptor) {
@@ -779,6 +780,7 @@
             public void onEndpointsStarted(HubEndpointInfo[] hubEndpointInfoList) {
                 if (hubEndpointInfoList.length == 0) {
                     Log.w(TAG, "onEndpointsStarted: received empty discovery list");
+                    invokeCallbackFinished(service);
                     return;
                 }
                 executor.execute(
@@ -791,6 +793,7 @@
                             } else {
                                 callback.onEndpointsStarted(discoveryList);
                             }
+                            invokeCallbackFinished(service);
                         });
             }
 
@@ -798,6 +801,7 @@
             public void onEndpointsStopped(HubEndpointInfo[] hubEndpointInfoList, int reason) {
                 if (hubEndpointInfoList.length == 0) {
                     Log.w(TAG, "onEndpointsStopped: received empty discovery list");
+                    invokeCallbackFinished(service);
                     return;
                 }
                 executor.execute(
@@ -810,8 +814,17 @@
                             } else {
                                 callback.onEndpointsStopped(discoveryList, reason);
                             }
+                            invokeCallbackFinished(service);
                         });
             }
+
+            private void invokeCallbackFinished(IContextHubService service) {
+                try {
+                    service.onDiscoveryCallbackFinished();
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
         };
     }
 
@@ -873,7 +886,7 @@
         Objects.requireNonNull(executor, "executor cannot be null");
         Objects.requireNonNull(callback, "callback cannot be null");
         IContextHubEndpointDiscoveryCallback iCallback =
-                createDiscoveryCallback(executor, callback, null);
+                createDiscoveryCallback(mService, executor, callback, null);
         try {
             mService.registerEndpointDiscoveryCallbackId(endpointId, iCallback);
         } catch (RemoteException e) {
@@ -919,7 +932,7 @@
         }
 
         IContextHubEndpointDiscoveryCallback iCallback =
-                createDiscoveryCallback(executor, callback, serviceDescriptor);
+                createDiscoveryCallback(mService, executor, callback, serviceDescriptor);
         try {
             mService.registerEndpointDiscoveryCallbackDescriptor(serviceDescriptor, iCallback);
         } catch (RemoteException e) {
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index d5b3fa2..bb5491d 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -150,4 +150,8 @@
     // Unregister an endpoint with the context hub
     @EnforcePermission("ACCESS_CONTEXT_HUB")
     void unregisterEndpointDiscoveryCallback(in IContextHubEndpointDiscoveryCallback callback);
+
+    // Called when a discovery callback is finished executing
+    @EnforcePermission("ACCESS_CONTEXT_HUB")
+    void onDiscoveryCallbackFinished();
 }
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index 0541a96..69b6597 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -16,15 +16,22 @@
 
 package android.os;
 
+import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ProcessInfo;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.Zygote;
 
 import dalvik.system.VMRuntime;
 
+import java.util.Map;
+
 /**
  * AppZygote is responsible for interfacing with an application-specific zygote.
  *
@@ -94,12 +101,90 @@
         return mAppInfo;
     }
 
+    /**
+     * Start a new process.
+     *
+     * <p>Wrap ZygoteProcess.start with retry logic.
+     *
+     * @param processClass The class to use as the process's main entry
+     *                     point.
+     * @param niceName A more readable name to use for the process.
+     * @param uid The user-id under which the process will run.
+     * @param gids Additional group-ids associated with the process.
+     * @param runtimeFlags Additional flags.
+     * @param targetSdkVersion The target SDK version for the app.
+     * @param seInfo null-ok SELinux information for the new process.
+     * @param abi non-null the ABI this app should be started with.
+     * @param instructionSet null-ok the instruction set to use.
+     * @param appDataDir null-ok the data directory of the app.
+     * @param packageName null-ok the name of the package this process belongs to.
+     * @param isTopApp Whether the process starts for high priority application.
+     * @param disabledCompatChanges null-ok list of disabled compat changes for the process being
+     *                             started.
+     * @param pkgDataInfoMap Map from related package names to private data directory
+     *                       volume UUID and inode number.
+     * @param allowlistedDataInfoList Map from allowlisted package names to private data directory
+     *                       volume UUID and inode number.
+     * @param zygoteArgs Additional arguments to supply to the Zygote process.
+     * @return An object that describes the result of the attempt to start the process.
+     * @throws RuntimeException on fatal start failure
+     */
+    public final Process.ProcessStartResult startProcess(@NonNull final String processClass,
+            final String niceName,
+            int uid, @Nullable int[] gids,
+            int runtimeFlags, int mountExternal,
+            int targetSdkVersion,
+            @Nullable String seInfo,
+            @NonNull String abi,
+            @Nullable String instructionSet,
+            @Nullable String appDataDir,
+            @Nullable String packageName,
+            boolean isTopApp,
+            @Nullable long[] disabledCompatChanges,
+            @Nullable Map<String, Pair<String, Long>>
+            pkgDataInfoMap,
+            @Nullable Map<String, Pair<String, Long>>
+            allowlistedDataInfoList,
+            @Nullable String[] zygoteArgs) {
+        try {
+            return getProcess().start(processClass,
+                    niceName, uid, uid, gids, runtimeFlags, mountExternal,
+                    targetSdkVersion, seInfo, abi, instructionSet,
+                    appDataDir, null, packageName,
+                    /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
+                    disabledCompatChanges, pkgDataInfoMap, allowlistedDataInfoList,
+                    false, false, false,
+                    zygoteArgs);
+        } catch (RuntimeException e) {
+            if (!Flags.appZygoteRetryStart()) {
+                throw e;
+            }
+            final boolean zygote_dead = getProcess().isDead();
+            if (!zygote_dead) {
+                throw e; // Zygote process is alive. Do nothing.
+            }
+        }
+        // Retry here if the previous start fails.
+        Log.w(LOG_TAG, "retry starting process " + niceName);
+        stopZygote();
+        return getProcess().start(processClass,
+                niceName, uid, uid, gids, runtimeFlags, mountExternal,
+                targetSdkVersion, seInfo, abi, instructionSet,
+                appDataDir, null, packageName,
+                /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
+                disabledCompatChanges, pkgDataInfoMap, allowlistedDataInfoList,
+                false, false, false,
+                zygoteArgs);
+    }
+
     @GuardedBy("mLock")
     private void stopZygoteLocked() {
         if (mZygote != null) {
             mZygote.close();
             // use killProcessGroup() here, so we kill all untracked children as well.
-            Process.killProcessGroup(mZygoteUid, mZygote.getPid());
+            if (!mZygote.isDead()) {
+                Process.killProcessGroup(mZygoteUid, mZygote.getPid());
+            }
             mZygote = null;
         }
     }
diff --git a/core/java/android/os/ChildZygoteProcess.java b/core/java/android/os/ChildZygoteProcess.java
index 337a3e2..d8f825a 100644
--- a/core/java/android/os/ChildZygoteProcess.java
+++ b/core/java/android/os/ChildZygoteProcess.java
@@ -17,6 +17,10 @@
 package android.os;
 
 import android.net.LocalSocketAddress;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Represents a connection to a child-zygote process. A child-zygote is spawend from another
@@ -30,9 +34,23 @@
      */
     private final int mPid;
 
-    ChildZygoteProcess(LocalSocketAddress socketAddress, int pid) {
+    /**
+     * The UID of the child zygote process.
+     */
+    private final int mUid;
+
+
+    /**
+     * If this zygote process was dead;
+     */
+    private AtomicBoolean mDead;
+
+
+    ChildZygoteProcess(LocalSocketAddress socketAddress, int pid, int uid) {
         super(socketAddress, null);
         mPid = pid;
+        mUid = uid;
+        mDead = new AtomicBoolean(false);
     }
 
     /**
@@ -41,4 +59,22 @@
     public int getPid() {
         return mPid;
     }
+
+    /**
+     * Check if child-zygote process is dead
+     */
+    public boolean isDead() {
+        if (mDead.get()) {
+            return true;
+        }
+        try {
+            if (Os.stat("/proc/" + mPid).st_uid == mUid) {
+                return false;
+            }
+        } catch (ErrnoException e) {
+            // Do nothing, it's dead.
+        }
+        mDead.set(true);
+        return true;
+    }
 }
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 4aa7462..c6a63a7 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -50,6 +50,7 @@
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
+import dalvik.annotation.optimization.NeverInline;
 
 import libcore.util.SneakyThrow;
 
@@ -628,6 +629,19 @@
         }
     }
 
+    @NeverInline
+    private void errorUsedWhileRecycling() {
+        String error = "Parcel used while recycled. "
+                + Log.getStackTraceString(new Throwable())
+                + " Original recycle call (if DEBUG_RECYCLE): ", mStack;
+        Log.wtf(TAG, error);
+        // TODO(b/381155347): harder error
+    }
+
+    private void assertNotRecycled() {
+        if (mRecycled) errorUsedWhileRecycling();
+    }
+
     /**
      * Set a {@link ReadWriteHelper}, which can be used to avoid having duplicate strings, for
      * example.
@@ -1180,6 +1194,7 @@
      * growing dataCapacity() if needed.
      */
     public final void writeInt(int val) {
+        assertNotRecycled();
         int err = nativeWriteInt(mNativePtr, val);
         if (err != OK) {
             nativeSignalExceptionForError(err);
@@ -3282,6 +3297,7 @@
      * Read an integer value from the parcel at the current dataPosition().
      */
     public final int readInt() {
+        assertNotRecycled();
         return nativeReadInt(mNativePtr);
     }
 
diff --git a/core/java/android/os/PerfettoTrace.java b/core/java/android/os/PerfettoTrace.java
index 164561a..e3f251e 100644
--- a/core/java/android/os/PerfettoTrace.java
+++ b/core/java/android/os/PerfettoTrace.java
@@ -22,7 +22,6 @@
 import libcore.util.NativeAllocationRegistry;
 
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
 
 /**
  * Writes trace events to the perfetto trace buffer. These trace events can be
@@ -72,7 +71,7 @@
          * @param name The category name.
          */
         public Category(String name) {
-            this(name, null, null);
+            this(name, "", "");
         }
 
         /**
@@ -82,7 +81,7 @@
          * @param tag An atrace tag name that this category maps to.
          */
         public Category(String name, String tag) {
-            this(name, tag, null);
+            this(name, tag, "");
         }
 
         /**
@@ -155,9 +154,6 @@
         }
     }
 
-    @FastNative
-    private static native void native_event(int type, long tag, String name, long ptr);
-
     @CriticalNative
     private static native long native_get_process_track_uuid();
 
@@ -170,176 +166,98 @@
     /**
      * Writes a trace message to indicate a given section of code was invoked.
      *
-     * @param category The perfetto category pointer.
-     * @param eventName The event name to appear in the trace.
-     * @param extra The extra arguments.
-     */
-    public static void instant(Category category, String eventName, PerfettoTrackEventExtra extra) {
-        if (!category.isEnabled()) {
-            return;
-        }
-
-        native_event(PERFETTO_TE_TYPE_INSTANT, category.getPtr(), eventName, extra.getPtr());
-        extra.reset();
-    }
-
-    /**
-     * Writes a trace message to indicate a given section of code was invoked.
-     *
      * @param category The perfetto category.
      * @param eventName The event name to appear in the trace.
-     * @param extraConfig Consumer for the extra arguments.
      */
-    public static void instant(Category category, String eventName,
-            Consumer<PerfettoTrackEventExtra.Builder> extraConfig) {
-        PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder();
-        extraConfig.accept(extra);
-        instant(category, eventName, extra.build());
+    public static PerfettoTrackEventExtra.Builder instant(Category category, String eventName) {
+        if (!category.isEnabled()) {
+            return PerfettoTrackEventExtra.noOpBuilder();
+        }
+
+        return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_INSTANT, category)
+            .setEventName(eventName);
     }
 
     /**
-     * Writes a trace message to indicate a given section of code was invoked.
+     * Writes a trace message to indicate the start of a given section of code.
      *
      * @param category The perfetto category.
      * @param eventName The event name to appear in the trace.
      */
-    public static void instant(Category category, String eventName) {
-        instant(category, eventName, PerfettoTrackEventExtra.builder().build());
-    }
-
-    /**
-     * Writes a trace message to indicate the start of a given section of code.
-     *
-     * @param category The perfetto category pointer.
-     * @param eventName The event name to appear in the trace.
-     * @param extra The extra arguments.
-     */
-    public static void begin(Category category, String eventName, PerfettoTrackEventExtra extra) {
+    public static PerfettoTrackEventExtra.Builder begin(Category category, String eventName) {
         if (!category.isEnabled()) {
-            return;
+            return PerfettoTrackEventExtra.noOpBuilder();
         }
 
-        native_event(PERFETTO_TE_TYPE_SLICE_BEGIN, category.getPtr(), eventName, extra.getPtr());
-        extra.reset();
-    }
-
-    /**
-     * Writes a trace message to indicate the start of a given section of code.
-     *
-     * @param category The perfetto category pointer.
-     * @param eventName The event name to appear in the trace.
-     * @param extraConfig Consumer for the extra arguments.
-     */
-    public static void begin(Category category, String eventName,
-            Consumer<PerfettoTrackEventExtra.Builder> extraConfig) {
-        PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder();
-        extraConfig.accept(extra);
-        begin(category, eventName, extra.build());
-    }
-
-    /**
-     * Writes a trace message to indicate the start of a given section of code.
-     *
-     * @param category The perfetto category pointer.
-     * @param eventName The event name to appear in the trace.
-     */
-    public static void begin(Category category, String eventName) {
-        begin(category, eventName, PerfettoTrackEventExtra.builder().build());
+        return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_BEGIN, category)
+            .setEventName(eventName);
     }
 
     /**
      * Writes a trace message to indicate the end of a given section of code.
      *
-     * @param category The perfetto category pointer.
-     * @param extra The extra arguments.
+     * @param category The perfetto category.
      */
-    public static void end(Category category, PerfettoTrackEventExtra extra) {
+    public static PerfettoTrackEventExtra.Builder end(Category category) {
         if (!category.isEnabled()) {
-            return;
+            return PerfettoTrackEventExtra.noOpBuilder();
         }
 
-        native_event(PERFETTO_TE_TYPE_SLICE_END, category.getPtr(), "", extra.getPtr());
-        extra.reset();
-    }
-
-    /**
-     * Writes a trace message to indicate the end of a given section of code.
-     *
-     * @param category The perfetto category pointer.
-     * @param extraConfig Consumer for the extra arguments.
-     */
-    public static void end(Category category,
-            Consumer<PerfettoTrackEventExtra.Builder> extraConfig) {
-        PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder();
-        extraConfig.accept(extra);
-        end(category, extra.build());
-    }
-
-    /**
-     * Writes a trace message to indicate the end of a given section of code.
-     *
-     * @param category The perfetto category pointer.
-     */
-    public static void end(Category category) {
-        end(category, PerfettoTrackEventExtra.builder().build());
+        return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_END, category);
     }
 
     /**
      * Writes a trace message to indicate the value of a given section of code.
      *
-     * @param category The perfetto category pointer.
-     * @param extra The extra arguments.
-     */
-    public static void counter(Category category, PerfettoTrackEventExtra extra) {
-        if (!category.isEnabled()) {
-            return;
-        }
-
-        native_event(PERFETTO_TE_TYPE_COUNTER, category.getPtr(), "", extra.getPtr());
-        extra.reset();
-    }
-
-    /**
-     * Writes a trace message to indicate the value of a given section of code.
-     *
-     * @param category The perfetto category pointer.
-     * @param extraConfig Consumer for the extra arguments.
-     */
-    public static void counter(Category category,
-            Consumer<PerfettoTrackEventExtra.Builder> extraConfig) {
-        PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder();
-        extraConfig.accept(extra);
-        counter(category, extra.build());
-    }
-
-    /**
-     * Writes a trace message to indicate the value of a given section of code.
-     *
-     * @param category The perfetto category pointer.
-     * @param trackName The trackName for the event.
+     * @param category The perfetto category.
      * @param value The value of the counter.
      */
-    public static void counter(Category category, String trackName, long value) {
-        PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder()
-                .usingCounterTrack(trackName, PerfettoTrace.getProcessTrackUuid())
-                .setCounter(value)
-                .build();
-        counter(category, extra);
+    public static PerfettoTrackEventExtra.Builder counter(Category category, long value) {
+        if (!category.isEnabled()) {
+            return PerfettoTrackEventExtra.noOpBuilder();
+        }
+
+        return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category)
+            .setCounter(value);
     }
 
     /**
      * Writes a trace message to indicate the value of a given section of code.
      *
-     * @param category The perfetto category pointer.
+     * @param category The perfetto category.
+     * @param value The value of the counter.
      * @param trackName The trackName for the event.
+     */
+    public static PerfettoTrackEventExtra.Builder counter(
+            Category category, long value, String trackName) {
+        return counter(category, value).usingProcessCounterTrack(trackName);
+    }
+
+    /**
+     * Writes a trace message to indicate the value of a given section of code.
+     *
+     * @param category The perfetto category.
      * @param value The value of the counter.
      */
-    public static void counter(Category category, String trackName, double value) {
-        PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder()
-                .usingCounterTrack(trackName, PerfettoTrace.getProcessTrackUuid())
-                .setCounter(value)
-                .build();
-        counter(category, extra);
+    public static PerfettoTrackEventExtra.Builder counter(Category category, double value) {
+        if (!category.isEnabled()) {
+            return PerfettoTrackEventExtra.noOpBuilder();
+        }
+
+        return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category)
+            .setCounter(value);
+    }
+
+    /**
+     * Writes a trace message to indicate the value of a given section of code.
+     *
+     * @param category The perfetto category.
+     * @param value The value of the counter.
+     * @param trackName The trackName for the event.
+     */
+    public static PerfettoTrackEventExtra.Builder counter(
+            Category category, double value, String trackName) {
+        return counter(category, value).usingProcessCounterTrack(trackName);
     }
 
     /**
@@ -360,7 +278,7 @@
      * Returns the process track uuid that can be used as a parent track uuid.
      */
     public static long getProcessTrackUuid() {
-        if (IS_FLAG_ENABLED) {
+        if (!IS_FLAG_ENABLED) {
             return 0;
         }
         return native_get_process_track_uuid();
@@ -370,7 +288,7 @@
      * Given a thread tid, returns the thread track uuid that can be used as a parent track uuid.
      */
     public static long getThreadTrackUuid(long tid) {
-        if (IS_FLAG_ENABLED) {
+        if (!IS_FLAG_ENABLED) {
             return 0;
         }
         return native_get_thread_track_uuid(tid);
@@ -380,7 +298,7 @@
      * Activates a trigger by name {@code triggerName} with expiry in {@code ttlMs}.
      */
     public static void activateTrigger(String triggerName, int ttlMs) {
-        if (IS_FLAG_ENABLED) {
+        if (!IS_FLAG_ENABLED) {
             return;
         }
         native_activate_trigger(triggerName, ttlMs);
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java
index a219b3b..e034fb3 100644
--- a/core/java/android/os/PerfettoTrackEventExtra.java
+++ b/core/java/android/os/PerfettoTrackEventExtra.java
@@ -31,6 +31,7 @@
  */
 public final class PerfettoTrackEventExtra {
     private static final int DEFAULT_EXTRA_CACHE_SIZE = 5;
+    private static final Builder NO_OP_BUILDER = new NoOpBuilder();
     private static final ThreadLocal<PerfettoTrackEventExtra> sTrackEventExtra =
             new ThreadLocal<PerfettoTrackEventExtra>() {
                 @Override
@@ -40,7 +41,6 @@
             };
     private static final AtomicLong sNamedTrackId = new AtomicLong();
 
-    private boolean mIsInUse;
     private CounterInt64 mCounterInt64;
     private CounterDouble mCounterDouble;
     private Proto mProto;
@@ -123,15 +123,299 @@
         }
     }
 
+    public interface Builder {
+        /**
+         * Emits the track event.
+         */
+        void emit();
+
+        /**
+         * Initialize the builder for a new trace event.
+         */
+        Builder init(int traceType, PerfettoTrace.Category category);
+
+        /**
+         * Sets the event name for the track event.
+         *
+         * @param eventName can contain a format string specifier, in which case, the
+         * {@code args} are the format arguments. If no {@code args} are provided,
+         * the {@code eventName} should be the string itself.
+         * @param args format arguments if {@code eventName} is specified.
+         */
+        Builder setEventName(String eventName, Object... args);
+
+        /**
+         * Adds a debug arg with key {@code name} and value {@code val}.
+         */
+        Builder addArg(String name, long val);
+
+        /**
+         * Adds a debug arg with key {@code name} and value {@code val}.
+         */
+        Builder addArg(String name, boolean val);
+
+        /**
+         * Adds a debug arg with key {@code name} and value {@code val}.
+         */
+        Builder addArg(String name, double val);
+
+        /**
+         * Adds a debug arg with key {@code name} and value {@code val}.
+         *
+         * @param val can contain a format string specifier, in which case, the
+         * {@code args} are the format arguments. If no {@code args} are provided,
+         * the {@code val} should be the string itself.
+         * @param args format arguments if {@code val} is specified.
+         */
+        Builder addArg(String name, String val, Object... args);
+
+        /**
+         * Adds a flow with {@code id}.
+         */
+        Builder addFlow(int id);
+
+        /**
+         * Adds a terminating flow with {@code id}.
+         */
+        Builder addTerminatingFlow(int id);
+
+        /**
+         * Adds the events to a named track instead of the thread track where the
+         * event occurred.
+         *
+         * @param name can contain a format string specifier, in which case, the
+         * {@code args} are the format arguments. If no {@code args} are provided,
+         * the {@code name} should be the string itself.
+         * @param args format arguments if {@code name} is specified.
+         */
+        Builder usingNamedTrack(long parentUuid, String name, Object... args);
+
+        /**
+         * Adds the events to a process scoped named track instead of the thread track where the
+         * event occurred.
+         *
+         * @param name can contain a format string specifier, in which case, the
+         * {@code args} are the format arguments. If no {@code args} are provided,
+         * the {@code name} should be the string itself.
+         * @param args format arguments if {@code name} is specified.
+         */
+        Builder usingProcessNamedTrack(String name, Object... args);
+
+        /**
+         * Adds the events to a thread scoped named track instead of the thread track where the
+         * event occurred.
+         *
+         * @param name can contain a format string specifier, in which case, the
+         * {@code args} are the format arguments. If no {@code args} are provided,
+         * the {@code name} should be the string itself.
+         * @param args format arguments if {@code name} is specified.
+         */
+        Builder usingThreadNamedTrack(long tid, String name, Object... args);
+
+        /**
+         * Adds the events to a counter track instead. This is required for
+         * setting counter values.
+         *
+         * @param name can contain a format string specifier, in which case, the
+         * {@code args} are the format arguments. If no {@code args} are provided,
+         * the {@code name} should be the string itself.
+         * @param args format arguments if {@code name} is specified.
+         */
+        Builder usingCounterTrack(long parentUuid, String name, Object... args);
+
+        /**
+         * Adds the events to a process scoped counter track instead. This is required for
+         * setting counter values.
+         *
+         * @param name can contain a format string specifier, in which case, the
+         * {@code args} are the format arguments. If no {@code args} are provided,
+         * the {@code name} should be the string itself.
+         * @param args format arguments if {@code eventName} is specified.
+         */
+        Builder usingProcessCounterTrack(String name, Object... args);
+
+        /**
+         * Adds the events to a thread scoped counter track instead. This is required for
+         * setting counter values.
+         *
+         * @param name can contain a format string specifier, in which case, the
+         * {@code args} are the format arguments. If no {@code args} are provided,
+         * the {@code name} should be the string itself.
+         * @param args format arguments if {@code name} is specified.
+         */
+        Builder usingThreadCounterTrack(long tid, String name, Object... args);
+
+        /**
+         * Sets a long counter value on the event.
+         *
+         */
+        Builder setCounter(long val);
+
+        /**
+         * Sets a double counter value on the event.
+         *
+         */
+        Builder setCounter(double val);
+
+        /**
+         * Adds a proto field with field id {@code id} and value {@code val}.
+         */
+        Builder addField(long id, long val);
+
+        /**
+         * Adds a proto field with field id {@code id} and value {@code val}.
+         */
+        Builder addField(long id, double val);
+
+        /**
+         * Adds a proto field with field id {@code id} and value {@code val}.
+         *
+         * @param val can contain a format string specifier, in which case, the
+         * {@code args} are the format arguments. If no {@code args} are provided,
+         * the {@code val} should be the string itself.
+         * @param args format arguments if {@code val} is specified.
+         */
+        Builder addField(long id, String val, Object... args);
+
+        /**
+         * Begins a proto field with field
+         * Fields can be added from this point and there must be a corresponding
+         * {@link endProto}.
+         *
+         * The proto field is a singleton and all proto fields get added inside the
+         * one {@link beginProto} and {@link endProto} within the {@link Builder}.
+         */
+        Builder beginProto();
+
+        /**
+         * Ends a proto field.
+         */
+        Builder endProto();
+
+        /**
+         * Begins a nested proto field with field id {@code id}.
+         * Fields can be added from this point and there must be a corresponding
+         * {@link endNested}.
+         */
+        Builder beginNested(long id);
+
+        /**
+         * Ends a nested proto field.
+         */
+        Builder endNested();
+    }
+
+    public static final class NoOpBuilder implements Builder {
+        @Override
+        public void emit() {}
+        @Override
+        public Builder init(int traceType, PerfettoTrace.Category category) {
+            return this;
+        }
+        @Override
+        public Builder setEventName(String eventName, Object... args) {
+            return this;
+        }
+        @Override
+        public Builder addArg(String name, long val) {
+            return this;
+        }
+        @Override
+        public Builder addArg(String name, boolean val) {
+            return this;
+        }
+        @Override
+        public Builder addArg(String name, double val) {
+            return this;
+        }
+        @Override
+        public Builder addArg(String name, String val, Object... args) {
+            return this;
+        }
+        @Override
+        public Builder addFlow(int id) {
+            return this;
+        }
+        @Override
+        public Builder addTerminatingFlow(int id) {
+            return this;
+        }
+        @Override
+        public Builder usingNamedTrack(long parentUuid, String name, Object... args) {
+            return this;
+        }
+        @Override
+        public Builder usingProcessNamedTrack(String name, Object... args) {
+            return this;
+        }
+        @Override
+        public Builder usingThreadNamedTrack(long tid, String name, Object... args) {
+            return this;
+        }
+        @Override
+        public Builder usingCounterTrack(long parentUuid, String name, Object... args) {
+            return this;
+        }
+        @Override
+        public Builder usingProcessCounterTrack(String name, Object... args) {
+            return this;
+        }
+        @Override
+        public Builder usingThreadCounterTrack(long tid, String name, Object... args) {
+            return this;
+        }
+        @Override
+        public Builder setCounter(long val) {
+            return this;
+        }
+        @Override
+        public Builder setCounter(double val) {
+            return this;
+        }
+        @Override
+        public Builder addField(long id, long val) {
+            return this;
+        }
+        @Override
+        public Builder addField(long id, double val) {
+            return this;
+        }
+        @Override
+        public Builder addField(long id, String val, Object... args) {
+            return this;
+        }
+        @Override
+        public Builder beginProto() {
+            return this;
+        }
+        @Override
+        public Builder endProto() {
+            return this;
+        }
+        @Override
+        public Builder beginNested(long id) {
+            return this;
+        }
+        @Override
+        public Builder endNested() {
+            return this;
+        }
+    }
+
     /**
      * Builder for Perfetto track event extras.
      */
-    public static final class Builder {
+    public static final class BuilderImpl implements Builder {
         // For performance reasons, we hold a reference to mExtra as a holder for
         // perfetto pointers being added. This way, we avoid an additional list to hold
         // the pointers in Java and we can pass them down directly to native code.
         private final PerfettoTrackEventExtra mExtra;
+
+        private int mTraceType;
+        private PerfettoTrace.Category mCategory;
+        private String mEventName;
         private boolean mIsBuilt;
+
         private Builder mParent;
         private FieldContainer mCurrentContainer;
 
@@ -151,16 +435,10 @@
         private final Pool<FieldString> mFieldStringCache;
         private final Pool<FieldNested> mFieldNestedCache;
         private final Pool<Flow> mFlowCache;
-        private final Pool<Builder> mBuilderCache;
+        private final Pool<BuilderImpl> mBuilderCache;
 
-        private Builder() {
-            this(sTrackEventExtra.get(), null, null);
-        }
-
-        private Builder(PerfettoTrackEventExtra extra, Builder parent, FieldContainer container) {
-            mExtra = extra;
-            mParent = parent;
-            mCurrentContainer = container;
+        private BuilderImpl() {
+            mExtra = sTrackEventExtra.get();
 
             mNamedTrackCache = mExtra.mNamedTrackCache;
             mCounterTrackCache = mExtra.mCounterTrackCache;
@@ -180,25 +458,39 @@
             mProto = mExtra.getProto();
         }
 
-        /**
-         * Builds the track event extra.
-         */
-        public PerfettoTrackEventExtra build() {
+        @Override
+        public void emit() {
             checkParent();
             mIsBuilt = true;
 
+            native_emit(mTraceType, mCategory.getPtr(), mEventName, mExtra.getPtr());
+            // Reset after emitting to free any the extras used to trace the event.
+            mExtra.reset();
+        }
+
+        @Override
+        public Builder init(int traceType, PerfettoTrace.Category category) {
+            mTraceType = traceType;
+            mCategory = category;
+            mEventName = "";
             mFieldInt64Cache.reset();
             mFieldDoubleCache.reset();
             mFieldStringCache.reset();
             mFieldNestedCache.reset();
             mBuilderCache.reset();
 
-            return mExtra;
+            mExtra.reset();
+            // Reset after on init in case the thread created builders without calling emit
+            return initInternal(this, null);
         }
 
-        /**
-         * Adds a debug arg with key {@code name} and value {@code val}.
-         */
+        @Override
+        public Builder setEventName(String eventName, Object... args) {
+            mEventName = toString(eventName, args);
+            return this;
+        }
+
+        @Override
         public Builder addArg(String name, long val) {
             checkParent();
             ArgInt64 arg = mArgInt64Cache.get(name.hashCode());
@@ -211,9 +503,7 @@
             return this;
         }
 
-        /**
-         * Adds a debug arg with key {@code name} and value {@code val}.
-         */
+        @Override
         public Builder addArg(String name, boolean val) {
             checkParent();
             ArgBool arg = mArgBoolCache.get(name.hashCode());
@@ -226,9 +516,7 @@
             return this;
         }
 
-        /**
-         * Adds a debug arg with key {@code name} and value {@code val}.
-         */
+        @Override
         public Builder addArg(String name, double val) {
             checkParent();
             ArgDouble arg = mArgDoubleCache.get(name.hashCode());
@@ -241,24 +529,20 @@
             return this;
         }
 
-        /**
-         * Adds a debug arg with key {@code name} and value {@code val}.
-         */
-        public Builder addArg(String name, String val) {
+        @Override
+        public Builder addArg(String name, String val, Object... args) {
             checkParent();
             ArgString arg = mArgStringCache.get(name.hashCode());
             if (arg == null || !arg.getName().equals(name)) {
                 arg = new ArgString(name);
                 mArgStringCache.put(name.hashCode(), arg);
             }
-            arg.setValue(val);
+            arg.setValue(toString(val, args));
             mExtra.addPerfettoPointer(arg);
             return this;
         }
 
-        /**
-         * Adds a flow with {@code id}.
-         */
+        @Override
         public Builder addFlow(int id) {
             checkParent();
             Flow flow = mFlowCache.get(Flow::new);
@@ -267,9 +551,7 @@
             return this;
         }
 
-        /**
-         * Adds a terminating flow with {@code id}.
-         */
+        @Override
         public Builder addTerminatingFlow(int id) {
             checkParent();
             Flow flow = mFlowCache.get(Flow::new);
@@ -278,12 +560,11 @@
             return this;
         }
 
-        /**
-         * Adds the events to a named track instead of the thread track where the
-         * event occurred.
-         */
-        public Builder usingNamedTrack(String name, long parentUuid) {
+        @Override
+        public Builder usingNamedTrack(long parentUuid, String name, Object... args) {
             checkParent();
+            name = toString(name, args);
+
             NamedTrack track = mNamedTrackCache.get(name.hashCode());
             if (track == null || !track.getName().equals(name)) {
                 track = new NamedTrack(name, parentUuid);
@@ -293,13 +574,21 @@
             return this;
         }
 
-        /**
-         * Adds the events to a counter track instead. This is required for
-         * setting counter values.
-         *
-         */
-        public Builder usingCounterTrack(String name, long parentUuid) {
+        @Override
+        public Builder usingProcessNamedTrack(String name, Object... args) {
+            return usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), name, args);
+        }
+
+        @Override
+        public Builder usingThreadNamedTrack(long tid, String name, Object... args) {
+            return usingNamedTrack(PerfettoTrace.getThreadTrackUuid(tid), name, args);
+        }
+
+        @Override
+        public Builder usingCounterTrack(long parentUuid, String name, Object... args) {
             checkParent();
+            name = toString(name, args);
+
             CounterTrack track = mCounterTrackCache.get(name.hashCode());
             if (track == null || !track.getName().equals(name)) {
                 track = new CounterTrack(name, parentUuid);
@@ -309,10 +598,17 @@
             return this;
         }
 
-        /**
-         * Sets a long counter value on the event.
-         *
-         */
+        @Override
+        public Builder usingProcessCounterTrack(String name, Object... args) {
+            return usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), name, args);
+        }
+
+        @Override
+        public Builder usingThreadCounterTrack(long tid, String name, Object... args) {
+            return usingCounterTrack(PerfettoTrace.getThreadTrackUuid(tid), name, args);
+        }
+
+        @Override
         public Builder setCounter(long val) {
             checkParent();
             mCounterInt64.setValue(val);
@@ -320,10 +616,7 @@
             return this;
         }
 
-        /**
-         * Sets a double counter value on the event.
-         *
-         */
+        @Override
         public Builder setCounter(double val) {
             checkParent();
             mCounterDouble.setValue(val);
@@ -331,9 +624,7 @@
             return this;
         }
 
-        /**
-         * Adds a proto field with field id {@code id} and value {@code val}.
-         */
+        @Override
         public Builder addField(long id, long val) {
             checkContainer();
             FieldInt64 field = mFieldInt64Cache.get(FieldInt64::new);
@@ -342,9 +633,7 @@
             return this;
         }
 
-        /**
-         * Adds a proto field with field id {@code id} and value {@code val}.
-         */
+        @Override
         public Builder addField(long id, double val) {
             checkContainer();
             FieldDouble field = mFieldDoubleCache.get(FieldDouble::new);
@@ -353,35 +642,24 @@
             return this;
         }
 
-        /**
-         * Adds a proto field with field id {@code id} and value {@code val}.
-         */
-        public Builder addField(long id, String val) {
+        @Override
+        public Builder addField(long id, String val, Object... args) {
             checkContainer();
             FieldString field = mFieldStringCache.get(FieldString::new);
-            field.setValue(id, val);
+            field.setValue(id, toString(val, args));
             mCurrentContainer.addField(field);
             return this;
         }
 
-        /**
-         * Begins a proto field with field
-         * Fields can be added from this point and there must be a corresponding
-         * {@link endProto}.
-         *
-         * The proto field is a singleton and all proto fields get added inside the
-         * one {@link beginProto} and {@link endProto} within the {@link Builder}.
-         */
+        @Override
         public Builder beginProto() {
             checkParent();
             mProto.clearFields();
             mExtra.addPerfettoPointer(mProto);
-            return mBuilderCache.get(Builder::new).init(this, mProto);
+            return mBuilderCache.get(BuilderImpl::new).initInternal(this, mProto);
         }
 
-        /**
-         * Ends a proto field.
-         */
+        @Override
         public Builder endProto() {
             if (mParent == null || mCurrentContainer == null) {
                 throw new IllegalStateException("No proto to end");
@@ -389,22 +667,16 @@
             return mParent;
         }
 
-        /**
-         * Begins a nested proto field with field id {@code id}.
-         * Fields can be added from this point and there must be a corresponding
-         * {@link endNested}.
-         */
+        @Override
         public Builder beginNested(long id) {
             checkContainer();
             FieldNested field = mFieldNestedCache.get(FieldNested::new);
             field.setId(id);
             mCurrentContainer.addField(field);
-            return mBuilderCache.get(Builder::new).init(this, field);
+            return mBuilderCache.get(BuilderImpl::new).initInternal(this, field);
         }
 
-        /**
-         * Ends a nested proto field.
-         */
+        @Override
         public Builder endNested() {
             if (mParent == null || mCurrentContainer == null) {
                 throw new IllegalStateException("No nested field to end");
@@ -412,21 +684,15 @@
             return mParent;
         }
 
-        /**
-         * Initializes a {@link Builder}.
-         */
-        public Builder init(Builder parent, FieldContainer container) {
+        private static String toString(String val, Object... args) {
+            return args == null || args.length == 0 ? val : String.format(val, args);
+        }
+
+        private Builder initInternal(Builder parent, FieldContainer container) {
             mParent = parent;
             mCurrentContainer = container;
             mIsBuilt = false;
 
-            if (mParent == null) {
-                if (mExtra.mIsInUse) {
-                    throw new IllegalStateException("Cannot create a new builder when another"
-                            + " extra is in use");
-                }
-                mExtra.mIsInUse = true;
-            }
             return this;
         }
 
@@ -439,9 +705,8 @@
 
         private void checkParent() {
             checkState();
-            if (mParent != null) {
-                throw new IllegalStateException(
-                    "This builder has already been used. Create a new builder for another event.");
+            if (!this.equals(mParent)) {
+                throw new IllegalStateException("Operation not supported for proto");
             }
         }
 
@@ -458,7 +723,14 @@
      * Start a {@link Builder} to build a {@link PerfettoTrackEventExtra}.
      */
     public static Builder builder() {
-        return sTrackEventExtra.get().mBuilderCache.get(Builder::new).init(null, null);
+        return sTrackEventExtra.get().mBuilderCache.get(BuilderImpl::new).initInternal(null, null);
+    }
+
+    /**
+     * Returns a no-op {@link Builder}. Useful if a category is disabled.
+     */
+    public static Builder noOpBuilder() {
+        return NO_OP_BUILDER;
     }
 
     private final RingBuffer<NamedTrack> mNamedTrackCache =
@@ -476,7 +748,7 @@
     private final Pool<FieldString> mFieldStringCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
     private final Pool<FieldNested> mFieldNestedCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
     private final Pool<Flow> mFlowCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
-    private final Pool<Builder> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
+    private final Pool<BuilderImpl> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
 
     private static final NativeAllocationRegistry sRegistry =
             NativeAllocationRegistry.createMalloced(
@@ -509,7 +781,6 @@
      */
     public void reset() {
         native_clear_args(mPtr);
-        mIsInUse = false;
     }
 
     private CounterInt64 getCounterInt64() {
@@ -1078,4 +1349,6 @@
     private static native void native_add_arg(long ptr, long extraPtr);
     @CriticalNative
     private static native void native_clear_args(long ptr);
+    @FastNative
+    private static native void native_emit(int type, long tag, String name, long ptr);
 }
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 73a74b2..2d487b1 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -1319,6 +1319,6 @@
             throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
         }
 
-        return new ChildZygoteProcess(serverAddress, result.pid);
+        return new ChildZygoteProcess(serverAddress, result.pid, uid);
     }
 }
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 8b83698..b12433a7 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -150,6 +150,13 @@
 }
 
 flag {
+     name: "app_zygote_retry_start"
+     namespace: "arc_next"
+     description: "Guard the new added retry logic in app zygote."
+     bug: "361799815"
+}
+
+flag {
     name: "battery_part_status_api"
     is_exported: true
     namespace: "phoenix"
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 70d8891..a480a3b 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -493,3 +493,9 @@
     bug: "341941666"
 }
 
+flag {
+    name: "sqlite_discrete_op_event_logging_enabled"
+    namespace: "permissions"
+    description: "Collect sqlite performance metrics for discrete ops."
+    bug: "377584611"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7d067fb..2e231e3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -353,6 +353,18 @@
      */
     public static final String ACTION_ONE_HANDED_SETTINGS =
             "android.settings.action.ONE_HANDED_SETTINGS";
+
+    /**
+     * Activity Action: Show Double tap power gesture Settings page.
+     * <p>
+     * Input: Nothing
+     * <p>
+     * Output: Nothing
+     * @hide
+     */
+    public static final String ACTION_DOUBLE_TAP_POWER_SETTINGS =
+            "android.settings.action.DOUBLE_TAP_POWER_SETTINGS";
+
     /**
      * The return values for {@link Settings.Config#set}
      * @hide
@@ -6318,6 +6330,17 @@
         public static final String TOUCHPAD_SYSTEM_GESTURES = "touchpad_system_gestures";
 
         /**
+         * Whether touchpad acceleration is enabled.
+         *
+         * When enabled, the speed of the pointer will increase as the user moves their
+         * finger faster on the touchpad.
+         *
+         * @hide
+         */
+        public static final String TOUCHPAD_ACCELERATION_ENABLED =
+                "touchpad_acceleration_enabled";
+
+        /**
          * Whether to enable reversed vertical scrolling for connected mice.
          *
          * When enabled, scrolling down on the mouse wheel will move the screen up and vice versa.
@@ -6612,6 +6635,7 @@
             PRIVATE_SETTINGS.add(TOUCHPAD_TAP_DRAGGING);
             PRIVATE_SETTINGS.add(TOUCHPAD_RIGHT_CLICK_ZONE);
             PRIVATE_SETTINGS.add(TOUCHPAD_SYSTEM_GESTURES);
+            PRIVATE_SETTINGS.add(TOUCHPAD_ACCELERATION_ENABLED);
             PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
             PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
             PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
@@ -9314,6 +9338,16 @@
                 "accessibility_autoclick_cursor_area_size";
 
         /**
+         * Setting that specifies whether minor cursor movement will be ignored when
+         * {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set.
+         *
+         * @see #ACCESSIBILITY_AUTOCLICK_ENABLED
+         * @hide
+         */
+        public static final String ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT =
+                "accessibility_autoclick_ignore_minor_cursor_movement";
+
+        /**
          * Whether or not larger size icons are used for the pointer of mouse/trackpad for
          * accessibility.
          * (0 = false, 1 = true)
@@ -12383,12 +12417,6 @@
         public static final String CAMERA_EXTENSIONS_FALLBACK = "camera_extensions_fallback";
 
         /**
-         * Controls whether contextual suggestions can be shown in the media controls.
-         * @hide
-         */
-        public static final String MEDIA_CONTROLS_RECOMMENDATION = "qs_media_recommend";
-
-        /**
          * Controls magnification mode when magnification is enabled via a system-wide triple tap
          * gesture or the accessibility shortcut.
          *
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index a558622..792e6ff 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -43,7 +43,7 @@
 
 flag {
     name: "secure_array_zeroization"
-    namespace: "platform_security"
+    namespace: "security"
     description: "Enable secure array zeroization"
     bug: "320392352"
     metadata {
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
index b5251db..051885e 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
 
 import java.io.Closeable;
 import java.util.concurrent.Executor;
@@ -232,6 +233,15 @@
     Drawable getTileIcon();
 
     /**
+     * Returns the user that should receive the wallet intents
+     *
+     * @return UserHandle
+     * @hide
+     */
+    @Nullable
+    UserHandle getUser();
+
+    /**
      * Returns the service label specified by {@code android:label} in the service manifest entry.
      *
      * @hide
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
index 97a4bef..1771642 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -243,8 +243,9 @@
             return null;
         }
         String packageName = mServiceInfo.getComponentName().getPackageName();
+        int userId = mServiceInfo.getUserId();
         String walletActivity = mServiceInfo.getWalletActivity();
-        return createIntent(walletActivity, packageName, ACTION_VIEW_WALLET);
+        return createIntent(walletActivity, packageName, userId, ACTION_VIEW_WALLET);
     }
 
     @Override
@@ -302,12 +303,15 @@
         }
         String packageName = mServiceInfo.getComponentName().getPackageName();
         String settingsActivity = mServiceInfo.getSettingsActivity();
-        return createIntent(settingsActivity, packageName, ACTION_VIEW_WALLET_SETTINGS);
+        return createIntent(settingsActivity, packageName, UserHandle.myUserId(),
+                ACTION_VIEW_WALLET_SETTINGS);
     }
 
     @Nullable
-    private Intent createIntent(@Nullable String activityName, String packageName, String action) {
-        PackageManager pm = mContext.getPackageManager();
+    private Intent createIntent(@Nullable String activityName, String packageName,
+            int userId, String action) {
+        Context userContext = mContext.createContextAsUser(UserHandle.of(userId), 0);
+        PackageManager pm = userContext.getPackageManager();
         if (TextUtils.isEmpty(activityName)) {
             activityName = queryActivityForAction(pm, packageName, action);
         }
@@ -361,6 +365,12 @@
         return mServiceInfo == null ? null : mServiceInfo.getTileIcon();
     }
 
+    @Nullable
+    @Override
+    public UserHandle getUser() {
+        return mServiceInfo == null ? null : UserHandle.of(mServiceInfo.getUserId());
+    }
+
     @Override
     @Nullable
     public CharSequence getServiceLabel() {
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index 8a3f6ce..e1dc6f6 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -16,6 +16,10 @@
 
 package android.service.quickaccesswallet;
 
+import static android.permission.flags.Flags.walletRoleCrossUserEnabled;
+
+import static com.android.permission.flags.Flags.crossUserRoleEnabled;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -32,10 +36,12 @@
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
 import android.os.Binder;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Xml;
 
 import com.android.internal.R;
@@ -59,22 +65,29 @@
     private final ServiceInfo mServiceInfo;
     private final ServiceMetadata mServiceMetadata;
     private final TileServiceMetadata mTileServiceMetadata;
+    private final int mUserId;
 
     private QuickAccessWalletServiceInfo(
             @NonNull ServiceInfo serviceInfo,
             @NonNull ServiceMetadata metadata,
-            @NonNull TileServiceMetadata tileServiceMetadata) {
+            @NonNull TileServiceMetadata tileServiceMetadata,
+            int userId) {
         mServiceInfo = serviceInfo;
         mServiceMetadata = metadata;
         mTileServiceMetadata = tileServiceMetadata;
+        mUserId = userId;
     }
 
     @Nullable
     static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
         String defaultAppPackageName = null;
 
+        int defaultAppUser = UserHandle.myUserId();
+
         if (isWalletRoleAvailable(context)) {
-            defaultAppPackageName = getDefaultWalletApp(context);
+            Pair<String, Integer> roleAndUser = getDefaultWalletApp(context);
+            defaultAppPackageName = roleAndUser.first;
+            defaultAppUser = roleAndUser.second;
         } else {
             ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
             if (defaultPaymentApp == null) {
@@ -83,7 +96,8 @@
             defaultAppPackageName = defaultPaymentApp.getPackageName();
         }
 
-        ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName);
+        ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName,
+                defaultAppUser);
         if (serviceInfo == null) {
             return null;
         }
@@ -98,15 +112,32 @@
         ServiceMetadata metadata = parseServiceMetadata(context, serviceInfo);
         TileServiceMetadata tileServiceMetadata =
                 new TileServiceMetadata(parseTileServiceMetadata(context, serviceInfo));
-        return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata);
+        return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata,
+                defaultAppUser);
     }
 
-    private static String getDefaultWalletApp(Context context) {
+    @NonNull
+    private static Pair<String, Integer> getDefaultWalletApp(Context context) {
+        UserHandle user = UserHandle.of(UserHandle.myUserId());
+
         final long token = Binder.clearCallingIdentity();
         try {
             RoleManager roleManager = context.getSystemService(RoleManager.class);
-            List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET);
-            return roleHolders.isEmpty() ? null : roleHolders.get(0);
+
+            if (walletRoleCrossUserEnabled()
+                    && crossUserRoleEnabled()
+                    && context.checkCallingOrSelfPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                    == PackageManager.PERMISSION_GRANTED) {
+                user = roleManager.getActiveUserForRole(RoleManager.ROLE_WALLET);
+                if (user == null) {
+                    return new Pair<>(null, UserHandle.myUserId());
+                }
+            }
+            List<String> roleHolders = roleManager.getRoleHoldersAsUser(RoleManager.ROLE_WALLET,
+                    user);
+            return new Pair<>(roleHolders.isEmpty() ? null : roleHolders.get(0),
+                    user.getIdentifier());
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -128,15 +159,16 @@
         return comp == null ? null : ComponentName.unflattenFromString(comp);
     }
 
-    private static ServiceInfo getWalletServiceInfo(Context context, String packageName) {
+    private static ServiceInfo getWalletServiceInfo(Context context, String packageName,
+            int userId) {
         Intent intent = new Intent(QuickAccessWalletService.SERVICE_INTERFACE);
         intent.setPackage(packageName);
         List<ResolveInfo> resolveInfos =
-                context.getPackageManager().queryIntentServices(intent,
+                context.getPackageManager().queryIntentServicesAsUser(intent,
                         PackageManager.MATCH_DIRECT_BOOT_AWARE
                         | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                         | PackageManager.MATCH_DEFAULT_ONLY
-                        | PackageManager.GET_META_DATA);
+                        | PackageManager.GET_META_DATA, userId);
         return resolveInfos.isEmpty() ? null : resolveInfos.get(0).serviceInfo;
     }
 
@@ -247,6 +279,9 @@
         return mServiceInfo.getComponentName();
     }
 
+    int getUserId() {
+        return mUserId;
+    }
     /**
      * @return the fully qualified name of the activity that hosts the full wallet. If available,
      * this intent should be started with the action
diff --git a/core/java/android/timezone/MobileCountries.java b/core/java/android/timezone/MobileCountries.java
new file mode 100644
index 0000000..19ae608
--- /dev/null
+++ b/core/java/android/timezone/MobileCountries.java
@@ -0,0 +1,68 @@
+/*
+ * 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 android.timezone;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Information about a telephony network.
+ *
+ * @hide
+ */
+public final class MobileCountries {
+
+    @NonNull
+    private final com.android.i18n.timezone.MobileCountries mDelegate;
+
+    MobileCountries(@NonNull com.android.i18n.timezone.MobileCountries delegate) {
+        mDelegate = Objects.requireNonNull(delegate);
+    }
+
+    /**
+     * Returns the Mobile Country Code of the network.
+     */
+    @NonNull
+    public String getMcc() {
+        return mDelegate.getMcc();
+    }
+
+    /**
+     * Returns the Mobile Country Code of the network.
+     */
+    @NonNull
+    public Set<String> getCountryIsoCodes() {
+        return mDelegate.getCountryIsoCodes();
+    }
+
+    /**
+     * Returns the country in which the network operates as an ISO 3166 alpha-2 (lower case).
+     */
+    @NonNull
+    public String getDefaultCountryIsoCode() {
+        return mDelegate.getDefaultCountryIsoCode();
+    }
+
+    @Override
+    public String toString() {
+        return "MobileCountries{"
+                + "mDelegate=" + mDelegate
+                + '}';
+    }
+}
diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java
index c69ddf8..eb50fc2 100644
--- a/core/java/android/timezone/TelephonyNetworkFinder.java
+++ b/core/java/android/timezone/TelephonyNetworkFinder.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import com.android.icu.Flags;
+
 import java.util.Objects;
 
 /**
@@ -50,4 +52,19 @@
         return telephonyNetworkDelegate != null
                 ? new TelephonyNetwork(telephonyNetworkDelegate) : null;
     }
+
+    /**
+     * Returns the countries where a given MCC is in use.
+     */
+    @Nullable
+    public MobileCountries findCountriesByMcc(@NonNull String mcc) {
+        if (!Flags.telephonyLookupMccExtension()) {
+            return null;
+        }
+        Objects.requireNonNull(mcc);
+
+        com.android.i18n.timezone.MobileCountries countriesByMcc =
+                mDelegate.findCountriesByMcc(mcc);
+        return countriesByMcc != null ? new MobileCountries(countriesByMcc) : null;
+    }
 }
diff --git a/core/java/android/util/proto/ProtoFieldFilter.java b/core/java/android/util/proto/ProtoFieldFilter.java
new file mode 100644
index 0000000..c3ae106
--- /dev/null
+++ b/core/java/android/util/proto/ProtoFieldFilter.java
@@ -0,0 +1,335 @@
+/*
+ * 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 android.util.proto;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.function.Predicate;
+
+/**
+ * A utility class that reads raw protobuf data from an InputStream
+ * and copies only those fields for which a given predicate returns true.
+ *
+ * <p>
+ * This is a low-level approach that does not fully decode fields
+ * (unless necessary to determine lengths). It simply:
+ * <ul>
+ *   <li>Parses each field's tag (varint for field number & wire type)</li>
+ *   <li>If {@code includeFn(fieldNumber) == true}, copies
+ *       the tag bytes and the field bytes directly to the output</li>
+ *   <li>Otherwise, skips that field in the input</li>
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * Because we do not re-encode, unknown or unrecognized fields are copied
+ * <i>verbatim</i> and remain exactly as in the input (useful for partial
+ * parsing or partial transformations).
+ * </p>
+ *
+ * <p>
+ * Note: This class only filters based on top-level field numbers. For length-delimited
+ * fields (including nested messages), the entire contents are either copied or skipped
+ * as a single unit. The class is not capable of nested filtering.
+ * </p>
+ *
+ * @hide
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+public class ProtoFieldFilter {
+
+    private static final int BUFFER_SIZE_BYTES = 4096;
+
+    private final Predicate<Integer> mFieldPredicate;
+    // General-purpose buffer for reading proto fields and their data
+    private final byte[] mBuffer;
+    // Buffer specifically designated to hold varint values (max 10 bytes in protobuf encoding)
+    private final byte[] mVarIntBuffer = new byte[10];
+
+    /**
+    * Constructs a ProtoFieldFilter with a predicate that considers depth.
+    *
+    * @param fieldPredicate A predicate returning true if the given fieldNumber should be
+    *                       included in the output.
+    * @param bufferSize The size of the internal buffer used for processing proto fields.
+    *                   Larger buffers may improve performance when processing large
+    *                   length-delimited fields.
+    */
+    public ProtoFieldFilter(Predicate<Integer> fieldPredicate, int bufferSize) {
+        this.mFieldPredicate = fieldPredicate;
+        this.mBuffer = new byte[bufferSize];
+    }
+
+    /**
+    * Constructs a ProtoFieldFilter with a predicate that considers depth and
+    * uses a default buffer size.
+    *
+    * @param fieldPredicate A predicate returning true if the given fieldNumber should be
+    *                       included in the output.
+    */
+    public ProtoFieldFilter(Predicate<Integer> fieldPredicate) {
+        this(fieldPredicate, BUFFER_SIZE_BYTES);
+    }
+
+    /**
+     * Reads raw protobuf data from {@code in} and writes only those fields
+     * passing {@code includeFn} to {@code out}. The predicate is given
+     * (fieldNumber, wireType) for each encountered field.
+     *
+     * @param in        The input stream of protobuf data
+     * @param out       The output stream to which we write the filtered protobuf
+     * @throws IOException If reading or writing fails, or if the protobuf data is corrupted
+     */
+    public void filter(InputStream in, OutputStream out) throws IOException {
+        int tagBytesLength;
+        while ((tagBytesLength = readRawVarint(in)) > 0) {
+            // Parse the varint loaded in mVarIntBuffer, through readRawVarint
+            long tagVal = parseVarint(mVarIntBuffer, tagBytesLength);
+            int fieldNumber = (int) (tagVal >>> ProtoStream.FIELD_ID_SHIFT);
+            int wireType   = (int) (tagVal & ProtoStream.WIRE_TYPE_MASK);
+
+            if (fieldNumber == 0) {
+                break;
+            }
+            if (mFieldPredicate.test(fieldNumber)) {
+                out.write(mVarIntBuffer, 0, tagBytesLength);
+                copyFieldData(in, out, wireType);
+            } else {
+                skipFieldData(in, wireType);
+            }
+        }
+    }
+
+    /**
+     * Reads a varint (up to 10 bytes) from the stream as raw bytes
+     * and returns it in a byte array. If the stream is at EOF, returns null.
+     *
+     * @param in The input stream
+     * @return the size of the varint bytes moved to mVarIntBuffer
+     * @throws IOException If an error occurs, or if we detect a malformed varint
+     */
+    private int readRawVarint(InputStream in) throws IOException {
+        // We attempt to read 1 byte. If none available => null
+        int b = in.read();
+        if (b < 0) {
+            return 0;
+        }
+        int count = 0;
+        mVarIntBuffer[count++] = (byte) b;
+        // If the continuation bit is set, we continue
+        while ((b & 0x80) != 0) {
+            // read next byte
+            b = in.read();
+            // EOF
+            if (b < 0) {
+                throw new IOException("Malformed varint: reached EOF mid-varint");
+            }
+            // max 10 bytes for varint 64
+            if (count >= 10) {
+                throw new IOException("Malformed varint: too many bytes (max 10)");
+            }
+            mVarIntBuffer[count++] = (byte) b;
+        }
+        return count;
+    }
+
+    /**
+     * Parses a varint from the given raw bytes and returns it as a long.
+     *
+     * @param rawVarint The bytes representing the varint
+     * @param byteLength The number of bytes to read from rawVarint
+     * @return The decoded long value
+     */
+    private static long parseVarint(byte[] rawVarint, int byteLength) throws IOException {
+        long result = 0;
+        int shift = 0;
+        for (int i = 0; i < byteLength; i++) {
+            result |= ((rawVarint[i] & 0x7F) << shift);
+            shift += 7;
+            if (shift > 63) {
+                throw new IOException("Malformed varint: exceeds 64 bits");
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Copies the wire data for a single field from {@code in} to {@code out},
+     * assuming we have already read the field's tag.
+     *
+     * @param in       The input stream (protobuf data)
+     * @param out      The output stream
+     * @param wireType The wire type (0=varint, 1=fixed64, 2=length-delim, 5=fixed32)
+     * @throws IOException if reading/writing fails or data is malformed
+     */
+    private void copyFieldData(InputStream in, OutputStream out, int wireType)
+            throws IOException {
+        switch (wireType) {
+            case ProtoStream.WIRE_TYPE_VARINT:
+                copyVarint(in, out);
+                break;
+            case ProtoStream.WIRE_TYPE_FIXED64:
+                copyFixed(in, out, 8);
+                break;
+            case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED:
+                copyLengthDelimited(in, out);
+                break;
+            case ProtoStream.WIRE_TYPE_FIXED32:
+                copyFixed(in, out, 4);
+                break;
+            // case WIRE_TYPE_START_GROUP:
+                // Not Supported
+            // case WIRE_TYPE_END_GROUP:
+                // Not Supported
+            default:
+                // Error or unrecognized wire type
+                throw new IOException("Unknown or unsupported wire type: " + wireType);
+        }
+    }
+
+    /**
+     * Skips the wire data for a single field from {@code in},
+     * assuming the field's tag was already read.
+     */
+    private void skipFieldData(InputStream in, int wireType) throws IOException {
+        switch (wireType) {
+            case ProtoStream.WIRE_TYPE_VARINT:
+                skipVarint(in);
+                break;
+            case ProtoStream.WIRE_TYPE_FIXED64:
+                skipBytes(in, 8);
+                break;
+            case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED:
+                skipLengthDelimited(in);
+                break;
+            case ProtoStream.WIRE_TYPE_FIXED32:
+                skipBytes(in, 4);
+                break;
+             // case WIRE_TYPE_START_GROUP:
+                // Not Supported
+            // case WIRE_TYPE_END_GROUP:
+                // Not Supported
+            default:
+                throw new IOException("Unknown or unsupported wire type: " + wireType);
+        }
+    }
+
+    /** Copies a varint (the field's value) from in to out. */
+    private static void copyVarint(InputStream in, OutputStream out) throws IOException {
+        while (true) {
+            int b = in.read();
+            if (b < 0) {
+                throw new IOException("EOF while copying varint");
+            }
+            out.write(b);
+            if ((b & 0x80) == 0) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * Copies exactly {@code length} bytes from {@code in} to {@code out}.
+     */
+    private void copyFixed(InputStream in, OutputStream out,
+                int length) throws IOException {
+        int toRead = length;
+        while (toRead > 0) {
+            int chunk = Math.min(toRead, mBuffer.length);
+            int readCount = in.read(mBuffer, 0, chunk);
+            if (readCount < 0) {
+                throw new IOException("EOF while copying fixed" + (length * 8) + " field");
+            }
+            out.write(mBuffer, 0, readCount);
+            toRead -= readCount;
+        }
+    }
+
+    /** Copies a length-delimited field */
+    private void copyLengthDelimited(InputStream in,
+                    OutputStream out) throws IOException {
+        // 1) read length varint (and copy)
+        int lengthVarintLength = readRawVarint(in);
+        if (lengthVarintLength <= 0) {
+            throw new IOException("EOF reading length for length-delimited field");
+        }
+        out.write(mVarIntBuffer, 0, lengthVarintLength);
+
+        long lengthVal = parseVarint(mVarIntBuffer, lengthVarintLength);
+        if (lengthVal < 0 || lengthVal > Integer.MAX_VALUE) {
+            throw new IOException("Invalid length for length-delimited field: " + lengthVal);
+        }
+
+        // 2) copy that many bytes
+        copyFixed(in, out, (int) lengthVal);
+    }
+
+    /** Skips a varint in the input (does not write anything). */
+    private static void skipVarint(InputStream in) throws IOException {
+        int bytesSkipped = 0;
+        while (true) {
+            int b = in.read();
+            if (b < 0) {
+                throw new IOException("EOF while skipping varint");
+            }
+            if ((b & 0x80) == 0) {
+                break;
+            }
+            bytesSkipped++;
+            if (bytesSkipped > 10) {
+                throw new IOException("Malformed varint: exceeds maximum length of 10 bytes");
+            }
+        }
+    }
+
+    /** Skips exactly n bytes. */
+    private void skipBytes(InputStream in, long n) throws IOException {
+        long skipped = in.skip(n);
+        // If skip fails, fallback to reading the remaining bytes
+        if (skipped < n) {
+            long bytesRemaining = n - skipped;
+
+            while (bytesRemaining > 0) {
+                int bytesToRead = (int) Math.min(bytesRemaining, mBuffer.length);
+                int bytesRead = in.read(mBuffer, 0, bytesToRead);
+                if (bytesRemaining < 0) {
+                    throw new IOException("EOF while skipping bytes");
+                }
+                bytesRemaining -= bytesRead;
+            }
+        }
+    }
+
+    /**
+     * Skips a length-delimited field.
+     * 1) read the length as varint,
+     * 2) skip that many bytes
+     */
+    private void skipLengthDelimited(InputStream in) throws IOException {
+        int lengthVarintLength = readRawVarint(in);
+        if (lengthVarintLength <= 0) {
+            throw new IOException("EOF reading length for length-delimited field");
+        }
+        long lengthVal = parseVarint(mVarIntBuffer, lengthVarintLength);
+        if (lengthVal < 0 || lengthVal > Integer.MAX_VALUE) {
+            throw new IOException("Invalid length to skip: " + lengthVal);
+        }
+        skipBytes(in, lengthVal);
+    }
+
+}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 992790e..053ccdd 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -250,11 +250,14 @@
 
     /**
      * Set flag to indicate that client is blocked waiting for buffer release and
-     * buffer stuffing recovery should soon begin.
+     * buffer stuffing recovery should soon begin. This is provided with the
+     * duration of time in nanoseconds that the client was blocked for.
      * @hide
      */
-    public void onWaitForBufferRelease() {
-        mBufferStuffingState.isStuffed.set(true);
+    public void onWaitForBufferRelease(long durationNanos) {
+        if (durationNanos > mLastFrameIntervalNanos / 2) {
+            mBufferStuffingState.isStuffed.set(true);
+        }
     }
 
     /**
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 780e761..dd32947 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -2391,4 +2391,9 @@
             }
         }
     }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return SurfaceView.class.getName();
+    }
 }
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index ebc86ee..0c6eaae 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -918,6 +918,11 @@
         mLastFrameTimeMillis = now;
     }
 
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return TextureView.class.getName();
+    }
+
     @UnsupportedAppUsage
     private final SurfaceTexture.OnFrameAvailableListener mUpdateListener =
             surfaceTexture -> {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d88b6d6..7206906 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28365,10 +28365,8 @@
                 if (android.os.Flags.adpfMeasureDuringInputEventBoost()) {
                     final boolean notifyRenderer = hasExpensiveMeasuresDuringInputEvent();
                     if (notifyRenderer) {
-                        Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                                "CPU_LOAD_UP: " + "hasExpensiveMeasuresDuringInputEvent");
-                        getViewRootImpl().notifyRendererOfExpensiveFrame();
-                        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                        getViewRootImpl().notifyRendererOfExpensiveFrame(
+                                "ADPF_SendHint: hasExpensiveMeasuresDuringInputEvent");
                     }
                 }
                 // measure ourselves, this should set the measured dimension flag back
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 64e7bec..cd8a85a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2968,6 +2968,20 @@
         }
     }
 
+    /**
+     * Same as notifyRendererOfExpensiveFrame(), but adding {@code reason} for tracing.
+     *
+     * @hide
+     */
+    public void notifyRendererOfExpensiveFrame(String reason) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, reason);
+        try {
+            notifyRendererOfExpensiveFrame();
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     void scheduleTraversals() {
         if (!mTraversalScheduled) {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 64277b1..d267c94 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -160,6 +160,9 @@
     /** @hide */
     public static final int AUTOCLICK_CURSOR_AREA_INCREMENT_SIZE = 20;
 
+    /** @hide */
+    public static final boolean AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT = false;
+
     /**
      * 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/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index 8baa55f..6e2e100 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -74,8 +74,8 @@
     }
 
     @Override
-    void flush(@FlushReason int reason) {
-        mParent.flush(reason);
+    void internalFlush(@FlushReason int reason) {
+        mParent.internalFlush(reason);
     }
 
     @Override
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 3f3484d..b7a77d7 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -663,7 +663,7 @@
     @UiThread
     public void flush(@FlushReason int reason) {
         if (mOptions.lite) return;
-        getMainContentCaptureSession().flush(reason);
+        getMainContentCaptureSession().internalFlush(reason);
     }
 
     /**
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 6bb2975..791a6f4 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -342,7 +342,7 @@
     /**
      * Flushes the buffered events to the service.
      */
-    abstract void flush(@FlushReason int reason);
+    abstract void internalFlush(@FlushReason int reason);
 
     /**
      * Sets the {@link ContentCaptureContext} associated with the session.
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index eddfc42d..29cae85 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -375,7 +375,7 @@
     void onDestroy() {
         clearAndRunOnContentCaptureThread(() -> {
             try {
-                flush(FLUSH_REASON_SESSION_FINISHED);
+                internalFlush(FLUSH_REASON_SESSION_FINISHED);
             } finally {
                 destroySession();
             }
@@ -623,7 +623,7 @@
                 flushReason = forceFlush ? FLUSH_REASON_FORCE_FLUSH : FLUSH_REASON_FULL;
         }
 
-        flush(flushReason);
+        internalFlush(flushReason);
     }
 
     private boolean hasStarted() {
@@ -687,15 +687,18 @@
             if (sVerbose) Log.v(TAG, "Nothing to flush");
             return;
         }
-        flush(reason);
+        internalFlush(reason);
     }
 
-    /** @hide */
+    /**
+     * Internal API to flush the buffered events to the service.
+     *
+     * Do not confuse this with the public API {@link #flush()}.
+     *
+     * @hide */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     @Override
-    public void flush(@FlushReason int reason) {
-        // TODO: b/380381249 renaming the internal APIs to prevent confusions between this and the
-        // public API.
+    public void internalFlush(@FlushReason int reason) {
         runOnContentCaptureThread(() -> flushImpl(reason));
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 0a83bdc..8fef2d7 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -766,7 +766,6 @@
      * Returns true if IME supports only virtual devices.
      * @hide
      */
-    @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME)
     @SystemApi
     public boolean isVirtualDeviceOnly() {
         return mIsVirtualDeviceOnly;
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index ed6ec32..3cc0042 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static android.view.accessibility.Flags.triStateChecked;
+
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -209,6 +211,10 @@
             mCheckedFromResource = false;
             mChecked = checked;
             refreshDrawableState();
+            if (triStateChecked()) {
+                notifyViewAccessibilityStateChangedIfNeeded(
+                        AccessibilityEvent.CONTENT_CHANGE_TYPE_CHECKED);
+            }
 
             // Avoid infinite recursions if setChecked() is called from a listener
             if (mBroadcasting) {
@@ -490,7 +496,12 @@
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfoInternal(info);
         info.setCheckable(true);
-        info.setChecked(mChecked);
+        if (triStateChecked()) {
+            info.setChecked(mChecked ? AccessibilityNodeInfo.CHECKED_STATE_TRUE :
+                    AccessibilityNodeInfo.CHECKED_STATE_FALSE);
+        } else {
+            info.setChecked(mChecked);
+        }
     }
 
     @Override
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 3e0161a..2056e22 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -57,8 +57,8 @@
  * Choosing the input type configures the keyboard type that is shown, acceptable characters,
  * and appearance of the edit text.
  * For example, if you want to accept a secret number, like a unique pin or serial number,
- * you can set inputType to "numericPassword".
- * An inputType of "numericPassword" results in an edit text that accepts numbers only,
+ * you can set inputType to {@link android.R.styleable#TextView_inputType numberPassword}.
+ * An input type of {@code numberPassword} results in an edit text that accepts numbers only,
  * shows a numeric keyboard when focused, and masks the text that is entered for privacy.
  * <p>
  * See the <a href="{@docRoot}guide/topics/ui/controls/text.html">Text Fields</a>
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 1748b9d..8934cf6 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -39,7 +39,12 @@
      */
     void unregisterTaskOrganizer(ITaskOrganizer organizer);
 
-    /** Creates a persistent root task in WM for a particular windowing-mode. */
+    /**
+    * Creates a persistent root task in WM for a particular windowing-mode.
+    *
+    * It may be removed using {@link #deleteRootTask} or through
+    * {@link WindowContainerTransaction#removeRootTask}.
+    */
     void createRootTask(int displayId, int windowingMode, IBinder launchCookie,
             boolean removeWithTaskOrganizer);
 
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 02f8e2f..ce0ccd5 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -614,6 +614,10 @@
     /**
      * Finds and removes a task and its children using its container token. The task is removed
      * from recents.
+     *
+     * If the task is a root task, its leaves are removed but the root task is not. Use
+     * {@link #removeRootTask(WindowContainerToken)} to remove the root task.
+     *
      * @param containerToken ContainerToken of Task to be removed
      */
     @NonNull
@@ -623,6 +627,19 @@
     }
 
     /**
+     * Finds and removes a root task created by an organizer and its leaves using its container
+     * token.
+     *
+     * @param containerToken ContainerToken of the root task to be removed
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction removeRootTask(@NonNull WindowContainerToken containerToken) {
+        mHierarchyOps.add(HierarchyOp.createForRemoveRootTask(containerToken.asBinder()));
+        return this;
+    }
+
+    /**
      * Sets whether a container is being drag-resized.
      * When {@code true}, the client will reuse a single (larger) surface size to avoid
      * continuous allocations on every size change.
@@ -1573,6 +1590,7 @@
         public static final int HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES = 21;
         public static final int HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE = 22;
         public static final int HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT = 23;
+        public static final int HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK = 24;
 
         @IntDef(prefix = {"HIERARCHY_OP_TYPE_"}, value = {
                 HIERARCHY_OP_TYPE_REPARENT,
@@ -1598,7 +1616,8 @@
                 HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION,
                 HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES,
                 HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE,
-                HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT
+                HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT,
+                HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK,
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface HierarchyOpType {
@@ -1795,6 +1814,18 @@
                     .build();
         }
 
+        /**
+         * Creates a hierarchy op for deleting a root task
+         *
+         * @hide
+         **/
+        @NonNull
+        public static HierarchyOp createForRemoveRootTask(@NonNull IBinder container) {
+            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK)
+                    .setContainer(container)
+                    .build();
+        }
+
         /** Creates a hierarchy op for clearing adjacent root tasks. */
         @NonNull
         public static HierarchyOp createForClearAdjacentRoots(@NonNull IBinder root) {
@@ -2012,6 +2043,7 @@
                     return "removeInsetsFrameProvider";
                 case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: return "setAlwaysOnTop";
                 case HIERARCHY_OP_TYPE_REMOVE_TASK: return "removeTask";
+                case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK: return "removeRootTask";
                 case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: return "finishActivity";
                 case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "clearAdjacentRoots";
                 case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
@@ -2096,6 +2128,9 @@
                 case HIERARCHY_OP_TYPE_REMOVE_TASK:
                     sb.append("task=").append(mContainer);
                     break;
+                case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK:
+                    sb.append("rootTask=").append(mContainer);
+                    break;
                 case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
                     sb.append("activity=").append(mContainer);
                     break;
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index f178b0e..1b946af 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -178,3 +178,10 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "safe_region_letterboxing"
+    description: "Enables letterboxing for a safe region"
+    bug: "380132497"
+}
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 158b526..928fa8c 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -265,8 +265,17 @@
      */
     public static final int CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS = 121;
 
+    /**
+     * Track closing task in Desktop Windowing.
+     *
+     * <p> Tracking begins when the CloseDesktopTaskTransitionHandler in Launcher starts
+     * animating the task closure. This is triggered when the close button in the app header is
+     * clicked on a desktop window. </p>
+     */
+    public static final int CUJ_DESKTOP_MODE_CLOSE_TASK = 122;
+
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
-    @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS;
+    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_CLOSE_TASK;
 
     /** @hide */
     @IntDef({
@@ -379,7 +388,8 @@
             CUJ_DESKTOP_MODE_SNAP_RESIZE,
             CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW,
             CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU,
-            CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS
+            CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS,
+            CUJ_DESKTOP_MODE_CLOSE_TASK
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {}
@@ -503,6 +513,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_UNMAXIMIZE_WINDOW;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OVERVIEW_TASK_DISMISS;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_CLOSE_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_CLOSE_TASK;
     }
 
     private Cuj() {
@@ -741,6 +752,8 @@
                 return "DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU";
             case CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS:
                 return "LAUNCHER_OVERVIEW_TASK_DISMISS";
+            case CUJ_DESKTOP_MODE_CLOSE_TASK:
+                return "DESKTOP_MODE_CLOSE_TASK";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 0d3c470..973fd7e 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -47,6 +47,7 @@
 import com.android.internal.widget.NotificationProgressDrawable.DrawableSegment;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -161,7 +162,7 @@
             final int progress = mProgressModel.getProgress();
             final int progressMax = mProgressModel.getProgressMax();
 
-            mParts = processAndConvertToViewParts(mProgressModel.getSegments(),
+            mParts = processModelAndConvertToViewParts(mProgressModel.getSegments(),
                     mProgressModel.getPoints(),
                     progress,
                     progressMax);
@@ -439,23 +440,107 @@
             return;
         }
 
-        mProgressDrawableParts = processAndConvertToDrawableParts(
+        final float segSegGap = mNotificationProgressDrawable.getSegSegGap();
+        final float segPointGap = mNotificationProgressDrawable.getSegPointGap();
+        final float pointRadius = mNotificationProgressDrawable.getPointRadius();
+        mProgressDrawableParts = processPartsAndConvertToDrawableParts(
                 mParts,
                 width,
-                mNotificationProgressDrawable.getSegSegGap(),
-                mNotificationProgressDrawable.getSegPointGap(),
-                mNotificationProgressDrawable.getPointRadius(),
+                segSegGap,
+                segPointGap,
+                pointRadius,
                 mHasTrackerIcon
         );
-        Pair<List<DrawablePart>, Float> p = maybeStretchAndRescaleSegments(
-                mParts,
-                mProgressDrawableParts,
-                mNotificationProgressDrawable.getSegmentMinWidth(),
-                mNotificationProgressDrawable.getPointRadius(),
-                getProgressFraction(),
-                width,
-                mProgressModel.isStyledByProgress(),
-                mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap());
+
+        final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth();
+        final float progressFraction = getProgressFraction();
+        final boolean isStyledByProgress = mProgressModel.isStyledByProgress();
+        final float progressGap =
+                mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap();
+        Pair<List<DrawablePart>, Float> p = null;
+        try {
+            p = maybeStretchAndRescaleSegments(
+                    mParts,
+                    mProgressDrawableParts,
+                    segmentMinWidth,
+                    pointRadius,
+                    progressFraction,
+                    width,
+                    isStyledByProgress,
+                    progressGap
+            );
+        } catch (NotEnoughWidthToFitAllPartsException ex) {
+            Log.w(TAG, "Failed to stretch and rescale segments", ex);
+        }
+
+        List<ProgressStyle.Segment> fallbackSegments = null;
+        if (p == null && mProgressModel.getSegments().size() > 1) {
+            Log.w(TAG, "Falling back to single segment");
+            try {
+                fallbackSegments = List.of(new ProgressStyle.Segment(getMax()).setColor(
+                        mProgressModel.getSegmentsFallbackColor()
+                                == NotificationProgressModel.INVALID_COLOR
+                                ? mProgressModel.getSegments().getFirst().getColor()
+                                : mProgressModel.getSegmentsFallbackColor()));
+                p = processModelAndConvertToFinalDrawableParts(
+                        fallbackSegments,
+                        mProgressModel.getPoints(),
+                        mProgressModel.getProgress(),
+                        getMax(),
+                        width,
+                        segSegGap,
+                        segPointGap,
+                        pointRadius,
+                        mHasTrackerIcon,
+                        segmentMinWidth,
+                        isStyledByProgress
+                );
+            } catch (NotEnoughWidthToFitAllPartsException ex) {
+                Log.w(TAG, "Failed to stretch and rescale segments with single segment fallback",
+                        ex);
+            }
+        }
+
+        if (p == null && !mProgressModel.getPoints().isEmpty()) {
+            Log.w(TAG, "Falling back to single segment and no points");
+            if (fallbackSegments == null) {
+                fallbackSegments = List.of(new ProgressStyle.Segment(getMax()).setColor(
+                        mProgressModel.getSegmentsFallbackColor()
+                                == NotificationProgressModel.INVALID_COLOR
+                                ? mProgressModel.getSegments().getFirst().getColor()
+                                : mProgressModel.getSegmentsFallbackColor()));
+            }
+            try {
+                p = processModelAndConvertToFinalDrawableParts(
+                        fallbackSegments,
+                        Collections.emptyList(),
+                        mProgressModel.getProgress(),
+                        getMax(),
+                        width,
+                        segSegGap,
+                        segPointGap,
+                        pointRadius,
+                        mHasTrackerIcon,
+                        segmentMinWidth,
+                        isStyledByProgress
+                );
+            } catch (NotEnoughWidthToFitAllPartsException ex) {
+                Log.w(TAG,
+                        "Failed to stretch and rescale segments with single segments and no points",
+                        ex);
+            }
+        }
+
+        if (p == null) {
+            Log.w(TAG, "Falling back to no stretching and rescaling");
+            p = maybeSplitDrawableSegmentsByProgress(
+                    mParts,
+                    mProgressDrawableParts,
+                    progressFraction,
+                    width,
+                    isStyledByProgress,
+                    progressGap);
+        }
 
         if (DEBUG) {
             Log.d(TAG, "Updating NotificationProgressDrawable parts");
@@ -502,7 +587,11 @@
         int min = getMin();
         int max = getMax();
         int range = max - min;
-        return range > 0 ? (getProgress() - min) / (float) range : 0;
+        return getProgressFraction(range, (getProgress() - min));
+    }
+
+    private static float getProgressFraction(int progressMax, int progress) {
+        return progressMax > 0 ? progress / (float) progressMax : 0;
     }
 
     /**
@@ -636,7 +725,7 @@
      * Processes the ProgressStyle data and convert to a list of {@code Part}.
      */
     @VisibleForTesting
-    public static List<Part> processAndConvertToViewParts(
+    public static List<Part> processModelAndConvertToViewParts(
             List<ProgressStyle.Segment> segments,
             List<ProgressStyle.Point> points,
             int progress,
@@ -796,7 +885,7 @@
      * Processes the list of {@code Part} and convert to a list of {@code DrawablePart}.
      */
     @VisibleForTesting
-    public static List<DrawablePart> processAndConvertToDrawableParts(
+    public static List<DrawablePart> processPartsAndConvertToDrawableParts(
             List<Part> parts,
             float totalWidth,
             float segSegGap,
@@ -823,7 +912,7 @@
                 // Retract the end position to account for the padding and a point immediately
                 // after.
                 final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
-                        segSegGap, iPart == nParts - 2, totalWidth, hasTrackerIcon);
+                        segSegGap, iPart == nParts - 2, hasTrackerIcon);
                 final float end = x + segWidth - endOffset;
 
                 drawableParts.add(new DrawableSegment(start, end, segment.mColor, segment.mFaded));
@@ -864,7 +953,7 @@
     }
 
     private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
-            float segPointGap, float segSegGap, boolean isSecondToLastPart, float totalWidth,
+            float segPointGap, float segSegGap, boolean isSecondToLastPart,
             boolean hasTrackerIcon) {
         if (nextPart == null) return 0F;
         if (nextPart instanceof Segment nextSeg) {
@@ -894,7 +983,7 @@
             float totalWidth,
             boolean isStyledByProgress,
             float progressGap
-    ) {
+    ) throws NotEnoughWidthToFitAllPartsException {
         final List<DrawableSegment> drawableSegments = drawableParts
                 .stream()
                 .filter(DrawableSegment.class::isInstance)
@@ -920,16 +1009,8 @@
         }
 
         if (totalExcessWidth < 0) {
-            // TODO: b/372908709 - throw an error so that the caller can catch and go to fallback
-            //  option. (instead of return.)
-            Log.w(TAG, "Not enough width to satisfy the minimum width for segments.");
-            return maybeSplitDrawableSegmentsByProgress(
-                    parts,
-                    drawableParts,
-                    progressFraction,
-                    totalWidth,
-                    isStyledByProgress,
-                    progressGap);
+            throw new NotEnoughWidthToFitAllPartsException(
+                    "Not enough width to satisfy the minimum width for segments.");
         }
 
         final int nParts = drawableParts.size();
@@ -1003,8 +1084,7 @@
         final int nParts = parts.size();
         for (int iPart = 0; iPart < nParts; iPart++) {
             final Part part = parts.get(iPart);
-            if (!(part instanceof Segment)) continue;
-            final Segment segment = (Segment) part;
+            if (!(part instanceof Segment segment)) continue;
             if (startFraction == progressFraction) {
                 iPartFirstSegmentToStyle = iPart;
                 rescaledProgressX = segment.mStart;
@@ -1066,11 +1146,37 @@
     }
 
     /**
+     * Processes the ProgressStyle data and convert to a pair of:
+     * - list of processed {@code DrawablePart}.
+     * - location of progress on the stretched and rescaled progress bar.
+     */
+    @VisibleForTesting
+    public static Pair<List<DrawablePart>, Float> processModelAndConvertToFinalDrawableParts(
+            List<ProgressStyle.Segment> segments,
+            List<ProgressStyle.Point> points,
+            int progress,
+            int progressMax,
+            float totalWidth,
+            float segSegGap,
+            float segPointGap,
+            float pointRadius,
+            boolean hasTrackerIcon,
+            float segmentMinWidth,
+            boolean isStyledByProgress
+    ) throws NotEnoughWidthToFitAllPartsException {
+        List<Part> parts = processModelAndConvertToViewParts(segments, points, progress,
+                progressMax);
+        List<DrawablePart> drawableParts = processPartsAndConvertToDrawableParts(parts, totalWidth,
+                segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        return maybeStretchAndRescaleSegments(parts, drawableParts, segmentMinWidth, pointRadius,
+                getProgressFraction(progressMax, progress), totalWidth, isStyledByProgress,
+                hasTrackerIcon ? 0F : segSegGap);
+    }
+
+    /**
      * A part of the progress bar, which is either a {@link Segment} with non-zero length, or a
      * {@link Point} with zero length.
      */
-    // TODO: b/372908709 - maybe this should be made private? Only test the final
-    //  NotificationDrawable.Parts.
     public interface Part {
     }
 
@@ -1176,4 +1282,10 @@
             return Objects.hash(mColor);
         }
     }
+
+    public static class NotEnoughWidthToFitAllPartsException extends Exception {
+        public NotEnoughWidthToFitAllPartsException(String message) {
+            super(message);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/widget/NotificationProgressModel.java b/core/java/com/android/internal/widget/NotificationProgressModel.java
index e8cb37e..7eaf861 100644
--- a/core/java/com/android/internal/widget/NotificationProgressModel.java
+++ b/core/java/com/android/internal/widget/NotificationProgressModel.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.widget;
 
-
 import android.annotation.ColorInt;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
@@ -45,24 +44,29 @@
  */
 @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
 public final class NotificationProgressModel {
-    private static final int INVALID_INDETERMINATE_COLOR = Color.TRANSPARENT;
+    public static final int INVALID_COLOR = Color.TRANSPARENT;
     private static final String KEY_SEGMENTS = "segments";
     private static final String KEY_POINTS = "points";
     private static final String KEY_PROGRESS = "progress";
     private static final String KEY_IS_STYLED_BY_PROGRESS = "isStyledByProgress";
+    private static final String KEY_SEGMENTS_FALLBACK_COLOR = "segmentsFallColor";
     private static final String KEY_INDETERMINATE_COLOR = "indeterminateColor";
     private final List<Segment> mSegments;
     private final List<Point> mPoints;
     private final int mProgress;
     private final boolean mIsStyledByProgress;
     @ColorInt
+    private final int mSegmentsFallbackColor;
+
+    @ColorInt
     private final int mIndeterminateColor;
 
     public NotificationProgressModel(
             @NonNull List<Segment> segments,
             @NonNull List<Point> points,
             int progress,
-            boolean isStyledByProgress
+            boolean isStyledByProgress,
+            @ColorInt int segmentsFallbackColor
     ) {
         Preconditions.checkArgument(progress >= 0);
         Preconditions.checkArgument(!segments.isEmpty());
@@ -70,17 +74,19 @@
         mPoints = points;
         mProgress = progress;
         mIsStyledByProgress = isStyledByProgress;
-        mIndeterminateColor = INVALID_INDETERMINATE_COLOR;
+        mSegmentsFallbackColor = segmentsFallbackColor;
+        mIndeterminateColor = INVALID_COLOR;
     }
 
     public NotificationProgressModel(
             @ColorInt int indeterminateColor
     ) {
-        Preconditions.checkArgument(indeterminateColor != INVALID_INDETERMINATE_COLOR);
+        Preconditions.checkArgument(indeterminateColor != INVALID_COLOR);
         mSegments = Collections.emptyList();
         mPoints = Collections.emptyList();
         mProgress = 0;
         mIsStyledByProgress = false;
+        mSegmentsFallbackColor = INVALID_COLOR;
         mIndeterminateColor = indeterminateColor;
     }
 
@@ -105,12 +111,17 @@
     }
 
     @ColorInt
+    public int getSegmentsFallbackColor() {
+        return mSegmentsFallbackColor;
+    }
+
+    @ColorInt
     public int getIndeterminateColor() {
         return mIndeterminateColor;
     }
 
     public boolean isIndeterminate() {
-        return mIndeterminateColor != INVALID_INDETERMINATE_COLOR;
+        return mIndeterminateColor != INVALID_COLOR;
     }
 
     /**
@@ -119,7 +130,7 @@
     @NonNull
     public Bundle toBundle() {
         final Bundle bundle = new Bundle();
-        if (mIndeterminateColor != INVALID_INDETERMINATE_COLOR) {
+        if (mIndeterminateColor != INVALID_COLOR) {
             bundle.putInt(KEY_INDETERMINATE_COLOR, mIndeterminateColor);
         } else {
             bundle.putParcelableList(KEY_SEGMENTS,
@@ -128,6 +139,9 @@
                     Notification.ProgressStyle.getProgressPointsAsBundleList(mPoints));
             bundle.putInt(KEY_PROGRESS, mProgress);
             bundle.putBoolean(KEY_IS_STYLED_BY_PROGRESS, mIsStyledByProgress);
+            if (mSegmentsFallbackColor != INVALID_COLOR) {
+                bundle.putInt(KEY_SEGMENTS_FALLBACK_COLOR, mSegmentsFallbackColor);
+            }
         }
         return bundle;
     }
@@ -138,8 +152,8 @@
     @NonNull
     public static NotificationProgressModel fromBundle(@NonNull Bundle bundle) {
         final int indeterminateColor = bundle.getInt(KEY_INDETERMINATE_COLOR,
-                INVALID_INDETERMINATE_COLOR);
-        if (indeterminateColor != INVALID_INDETERMINATE_COLOR) {
+                INVALID_COLOR);
+        if (indeterminateColor != INVALID_COLOR) {
             return new NotificationProgressModel(indeterminateColor);
         } else {
             final List<Segment> segments =
@@ -150,7 +164,10 @@
                             bundle.getParcelableArrayList(KEY_POINTS, Bundle.class));
             final int progress = bundle.getInt(KEY_PROGRESS);
             final boolean isStyledByProgress = bundle.getBoolean(KEY_IS_STYLED_BY_PROGRESS);
-            return new NotificationProgressModel(segments, points, progress, isStyledByProgress);
+            final int segmentsFallbackColor = bundle.getInt(KEY_SEGMENTS_FALLBACK_COLOR,
+                    INVALID_COLOR);
+            return new NotificationProgressModel(segments, points, progress, isStyledByProgress,
+                    segmentsFallbackColor);
         }
     }
 
@@ -161,6 +178,7 @@
                 + ", mPoints=" + mPoints
                 + ", mProgress=" + mProgress
                 + ", mIsStyledByProgress=" + mIsStyledByProgress
+                + ", mSegmentsFallbackColor=" + mSegmentsFallbackColor
                 + ", mIndeterminateColor=" + mIndeterminateColor + "}";
     }
 
@@ -171,6 +189,7 @@
         final NotificationProgressModel that = (NotificationProgressModel) o;
         return mProgress == that.mProgress
                 && mIsStyledByProgress == that.mIsStyledByProgress
+                && mSegmentsFallbackColor == that.mSegmentsFallbackColor
                 && mIndeterminateColor == that.mIndeterminateColor
                 && Objects.equals(mSegments, that.mSegments)
                 && Objects.equals(mPoints, that.mPoints);
@@ -182,6 +201,7 @@
                 mPoints,
                 mProgress,
                 mIsStyledByProgress,
+                mSegmentsFallbackColor,
                 mIndeterminateColor);
     }
 }
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 26b0d11..f5f4e43 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -62,7 +62,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<>();
 
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 9a37a22..0b6a3c4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -88,6 +88,8 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout;
+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.RowLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
@@ -97,6 +99,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
+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.HostActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostNamedActionOperation;
@@ -111,6 +114,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerChangeActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerExpressionChangeActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueStringChangeActionOperation;
+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.operations.layout.modifiers.ZIndexModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
@@ -208,7 +212,9 @@
     public static final int LAYOUT_CONTENT = 201;
     public static final int LAYOUT_BOX = 202;
     public static final int LAYOUT_ROW = 203;
+    public static final int LAYOUT_COLLAPSIBLE_ROW = 230;
     public static final int LAYOUT_COLUMN = 204;
+    public static final int LAYOUT_COLLAPSIBLE_COLUMN = 233;
     public static final int LAYOUT_CANVAS = 205;
     public static final int LAYOUT_CANVAS_CONTENT = 207;
     public static final int LAYOUT_TEXT = 208;
@@ -218,6 +224,8 @@
 
     public static final int MODIFIER_WIDTH = 16;
     public static final int MODIFIER_HEIGHT = 67;
+    public static final int MODIFIER_WIDTH_IN = 231;
+    public static final int MODIFIER_HEIGHT_IN = 232;
     public static final int MODIFIER_BACKGROUND = 55;
     public static final int MODIFIER_BORDER = 107;
     public static final int MODIFIER_PADDING = 58;
@@ -324,6 +332,8 @@
 
         map.put(MODIFIER_WIDTH, WidthModifierOperation::read);
         map.put(MODIFIER_HEIGHT, HeightModifierOperation::read);
+        map.put(MODIFIER_WIDTH_IN, WidthInModifierOperation::read);
+        map.put(MODIFIER_HEIGHT_IN, HeightInModifierOperation::read);
         map.put(MODIFIER_PADDING, PaddingModifierOperation::read);
         map.put(MODIFIER_BACKGROUND, BackgroundModifierOperation::read);
         map.put(MODIFIER_BORDER, BorderModifierOperation::read);
@@ -359,7 +369,9 @@
         map.put(LAYOUT_CONTENT, LayoutComponentContent::read);
         map.put(LAYOUT_BOX, BoxLayout::read);
         map.put(LAYOUT_COLUMN, ColumnLayout::read);
+        map.put(LAYOUT_COLLAPSIBLE_COLUMN, CollapsibleColumnLayout::read);
         map.put(LAYOUT_ROW, RowLayout::read);
+        map.put(LAYOUT_COLLAPSIBLE_ROW, CollapsibleRowLayout::read);
         map.put(LAYOUT_CANVAS, CanvasLayout::read);
         map.put(LAYOUT_CANVAS_CONTENT, CanvasContent::read);
         map.put(LAYOUT_TEXT, TextLayout::read);
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 39cc997..1cb8fef 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -86,6 +86,8 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout;
+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.RowLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
@@ -691,6 +693,12 @@
         return out;
     }
 
+    /**
+     * Append a path to an existing path
+     *
+     * @param id id of the path to append to
+     * @param path the path to append
+     */
     public void pathAppend(int id, float... path) {
         PathAppend.apply(mBuffer, id, path);
     }
@@ -772,8 +780,8 @@
      * @param text The text to be drawn
      * @param start The index of the first character in text to draw
      * @param end (end - 1) is the index of the last character in text to draw
-     * @param contextStart
-     * @param contextEnd
+     * @param contextStart the context start
+     * @param contextEnd the context end
      * @param x The x-coordinate of the origin of the text being drawn
      * @param y The y-coordinate of the baseline of the text being drawn
      * @param rtl Draw RTTL
@@ -798,8 +806,8 @@
      * @param textId The text to be drawn
      * @param start The index of the first character in text to draw
      * @param end (end - 1) is the index of the last character in text to draw
-     * @param contextStart
-     * @param contextEnd
+     * @param contextStart the context start
+     * @param contextEnd the context end
      * @param x The x-coordinate of the origin of the text being drawn
      * @param y The y-coordinate of the baseline of the text being drawn
      * @param rtl Draw RTTL
@@ -986,6 +994,11 @@
 
     ///////////////////////////////////////////////////////////////////////////////////////////////
 
+    /**
+     * inflate the buffer into a list of operations
+     *
+     * @param operations the operations list to add to
+     */
     public void inflateFromBuffer(@NonNull ArrayList<Operation> operations) {
         mBuffer.setIndex(0);
         while (mBuffer.available()) {
@@ -1001,6 +1014,12 @@
         }
     }
 
+    /**
+     * Read the next operation from the buffer
+     *
+     * @param buffer The buff to read
+     * @param operations the operations list to add to
+     */
     public static void readNextOperation(
             @NonNull WireBuffer buffer, @NonNull ArrayList<Operation> operations) {
         int opId = buffer.readByte();
@@ -1014,6 +1033,11 @@
         operation.read(buffer, operations);
     }
 
+    /**
+     * copy the current buffer to a new one
+     *
+     * @return A new RemoteComposeBuffer
+     */
     @NonNull
     RemoteComposeBuffer copy() {
         ArrayList<Operation> operations = new ArrayList<>();
@@ -1022,6 +1046,11 @@
         return copyFromOperations(operations, buffer);
     }
 
+    /**
+     * add a set theme
+     *
+     * @param theme The theme to set
+     */
     public void setTheme(int theme) {
         Theme.apply(mBuffer, theme);
     }
@@ -1040,6 +1069,14 @@
         return buffer;
     }
 
+    /**
+     * Create a RemoteComposeBuffer from a file
+     *
+     * @param file A file
+     * @param remoteComposeState The RemoteComposeState
+     * @return A RemoteComposeBuffer
+     * @throws IOException if the file cannot be read
+     */
     @NonNull
     public RemoteComposeBuffer fromFile(
             @NonNull File file, @NonNull RemoteComposeState remoteComposeState) throws IOException {
@@ -1048,6 +1085,13 @@
         return buffer;
     }
 
+    /**
+     * Create a RemoteComposeBuffer from an InputStream
+     *
+     * @param inputStream An InputStream
+     * @param remoteComposeState The RemoteComposeState
+     * @return A RemoteComposeBuffer
+     */
     @NonNull
     public static RemoteComposeBuffer fromInputStream(
             @NonNull InputStream inputStream, @NonNull RemoteComposeState remoteComposeState) {
@@ -1056,6 +1100,13 @@
         return buffer;
     }
 
+    /**
+     * Create a RemoteComposeBuffer from an array of operations
+     *
+     * @param operations An array of operations
+     * @param buffer A RemoteComposeBuffer
+     * @return A RemoteComposeBuffer
+     */
     @NonNull
     RemoteComposeBuffer copyFromOperations(
             @NonNull ArrayList<Operation> operations, @NonNull RemoteComposeBuffer buffer) {
@@ -1834,12 +1885,12 @@
     /**
      * Add a marquee modifier
      *
-     * @param iterations
-     * @param animationMode
-     * @param repeatDelayMillis
-     * @param initialDelayMillis
-     * @param spacing
-     * @param velocity
+     * @param iterations number of iterations
+     * @param animationMode animation mode
+     * @param repeatDelayMillis repeat delay
+     * @param initialDelayMillis initial delay
+     * @param spacing spacing between items
+     * @param velocity velocity of the marquee
      */
     public void addModifierMarquee(
             int iterations,
@@ -1861,14 +1912,21 @@
     /**
      * Add a graphics layer
      *
-     * @param scaleX
-     * @param scaleY
-     * @param rotationX
-     * @param rotationY
-     * @param rotationZ
-     * @param shadowElevation
-     * @param transformOriginX
-     * @param transformOriginY
+     * @param scaleX scale x
+     * @param scaleY scale y
+     * @param rotationX rotation in X
+     * @param rotationY rotation in Y
+     * @param rotationZ rotation in Z
+     * @param shadowElevation shadow elevation
+     * @param transformOriginX transform origin x
+     * @param transformOriginY transform origin y
+     * @param alpha alpha value
+     * @param cameraDistance camera distance
+     * @param blendMode blend mode
+     * @param spotShadowColorId spot shadow color
+     * @param ambientShadowColorId ambient shadow color
+     * @param colorFilterId id of color filter
+     * @param renderEffectId id of render effect
      */
     public void addModifierGraphicsLayer(
             float scaleX,
@@ -1923,14 +1981,32 @@
         ClipRectModifierOperation.apply(mBuffer);
     }
 
+    /**
+     * add start of loop
+     *
+     * @param indexId id of the variable
+     * @param from start value
+     * @param step step value
+     * @param until stop value
+     */
     public void addLoopStart(int indexId, float from, float step, float until) {
         LoopOperation.apply(mBuffer, indexId, from, step, until);
     }
 
+    /** Add a loop end */
     public void addLoopEnd() {
         ContainerEnd.apply(mBuffer);
     }
 
+    /**
+     * add a state layout
+     *
+     * @param componentId id of the state
+     * @param animationId animation id
+     * @param horizontal horizontal alignment
+     * @param vertical vertical alignment
+     * @param indexId index of the state
+     */
     public void addStateLayout(
             int componentId, int animationId, int horizontal, int vertical, int indexId) {
         mLastComponentId = getComponentId(componentId);
@@ -1966,6 +2042,22 @@
     }
 
     /**
+     * Add a row start tag
+     *
+     * @param componentId component id
+     * @param animationId animation id
+     * @param horizontal horizontal alignment
+     * @param vertical vertical alignment
+     * @param spacedBy spacing between items
+     */
+    public void addCollapsibleRowStart(
+            int componentId, int animationId, int horizontal, int vertical, float spacedBy) {
+        mLastComponentId = getComponentId(componentId);
+        CollapsibleRowLayout.apply(
+                mBuffer, mLastComponentId, animationId, horizontal, vertical, spacedBy);
+    }
+
+    /**
      * Add a column start tag
      *
      * @param componentId component id
@@ -1981,6 +2073,22 @@
     }
 
     /**
+     * Add a column start tag
+     *
+     * @param componentId component id
+     * @param animationId animation id
+     * @param horizontal horizontal alignment
+     * @param vertical vertical alignment
+     * @param spacedBy spacing between items
+     */
+    public void addCollapsibleColumnStart(
+            int componentId, int animationId, int horizontal, int vertical, float spacedBy) {
+        mLastComponentId = getComponentId(componentId);
+        CollapsibleColumnLayout.apply(
+                mBuffer, mLastComponentId, animationId, horizontal, vertical, spacedBy);
+    }
+
+    /**
      * Add a canvas start tag
      *
      * @param componentId component id
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 43f8ea7d..363b82b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -67,8 +67,8 @@
      * Get Object based on id. The system will cache things like bitmaps Paths etc. They can be
      * accessed with this command
      *
-     * @param id
-     * @return
+     * @param id the id of the object
+     * @return the object
      */
     @Nullable
     public Object getFromId(int id) {
@@ -78,8 +78,8 @@
     /**
      * true if the cache contain this id
      *
-     * @param id
-     * @return
+     * @param id the id of the object
+     * @return true if the cache contain this id
      */
     public boolean containsId(int id) {
         return mIntDataMap.get(id) != null;
@@ -138,8 +138,8 @@
     /**
      * Get the path asociated with the Data
      *
-     * @param id
-     * @return
+     * @param id of path
+     * @return path object
      */
     public Object getPath(int id) {
         return mPathMap.get(id);
@@ -180,7 +180,7 @@
     /**
      * Adds a data Override.
      *
-     * @param id
+     * @param id the id of the data
      * @param item the new value
      */
     public void overrideData(int id, @NonNull Object item) {
@@ -222,8 +222,8 @@
     /**
      * Adds a float Override.
      *
-     * @param id
-     * @param value the new value
+     * @param id The id of the float
+     * @param value the override value
      */
     public void overrideFloat(int id, float value) {
         float previous = mFloatMap.get(id);
@@ -235,7 +235,12 @@
         }
     }
 
-    /** Insert an item in the cache */
+    /**
+     * Insert an item in the cache
+     *
+     * @param item integer item to cache
+     * @return the id of the integer
+     */
     public int cacheInteger(int item) {
         int id = nextId();
         mIntegerMap.put(id, item);
@@ -243,7 +248,12 @@
         return id;
     }
 
-    /** Insert an integer item in the cache */
+    /**
+     * Insert an integer item in the cache
+     *
+     * @param id the id of the integer
+     * @param value the value of the integer
+     */
     public void updateInteger(int id, int value) {
         if (!mIntegerOverride[id]) {
             int previous = mIntegerMap.get(id);
@@ -292,10 +302,10 @@
     }
 
     /**
-     * Get the float value
+     * Get the color from the cache
      *
-     * @param id
-     * @return
+     * @param id The id of the color
+     * @return The color
      */
     public int getColor(int id) {
         return mColorMap.get(id);
@@ -377,6 +387,9 @@
     /**
      * Method to determine if a cached value has been written to the documents WireBuffer based on
      * its id.
+     *
+     * @param id id to check
+     * @return true if the value has not been written to the WireBuffer
      */
     public boolean wasNotWritten(int id) {
         return !mIntWrittenMap.get(id);
@@ -406,7 +419,7 @@
      * Get the next available id 0 is normal (float,int,String,color) 1 is VARIABLES 2 is
      * collections
      *
-     * @return
+     * @return return a unique id in the set
      */
     public int nextId(int type) {
         if (0 == type) {
@@ -418,7 +431,7 @@
     /**
      * Set the next id
      *
-     * @param id
+     * @param id set the id to increment off of
      */
     public void setNextId(int id) {
         mNextId = id;
@@ -440,8 +453,8 @@
     /**
      * Commands that listen to variables add themselves.
      *
-     * @param id
-     * @param variableSupport
+     * @param id id of variable to listen to
+     * @param variableSupport command that listens to variable
      */
     public void listenToVar(int id, @NonNull VariableSupport variableSupport) {
         add(id, variableSupport);
@@ -450,8 +463,8 @@
     /**
      * Is any command listening to this variable
      *
-     * @param id
-     * @return
+     * @param id The Variable id
+     * @return true if any command is listening to this variable
      */
     public boolean hasListener(int id) {
         return mVarListeners.get(id) != null;
@@ -460,8 +473,8 @@
     /**
      * List of Commands that need to be updated
      *
-     * @param context
-     * @return
+     * @param context The context
+     * @return The number of ops to update
      */
     public int getOpsToUpdate(@NonNull RemoteContext context) {
         if (mVarListeners.get(RemoteContext.ID_CONTINUOUS_SEC) != null) {
@@ -479,7 +492,7 @@
     /**
      * Set the width of the overall document on screen.
      *
-     * @param width
+     * @param width the width of the document in pixels
      */
     public void setWindowWidth(float width) {
         updateFloat(RemoteContext.ID_WINDOW_WIDTH, width);
@@ -488,12 +501,18 @@
     /**
      * Set the width of the overall document on screen.
      *
-     * @param height
+     * @param height the height of the document in pixels
      */
     public void setWindowHeight(float height) {
         updateFloat(RemoteContext.ID_WINDOW_HEIGHT, height);
     }
 
+    /**
+     * Add an array access
+     *
+     * @param id The id of the array Access
+     * @param collection The array access
+     */
     public void addCollection(int id, @NonNull ArrayAccess collection) {
         mCollectionMap.put(id & 0xFFFFF, collection);
     }
@@ -513,10 +532,22 @@
         return mCollectionMap.get(id & 0xFFFFF).getId(index);
     }
 
+    /**
+     * adds a DataMap to the cache
+     *
+     * @param id The id of the data map
+     * @param map The data map
+     */
     public void putDataMap(int id, @NonNull DataMap map) {
         mDataMapMap.put(id, map);
     }
 
+    /**
+     * Get the DataMap asociated with the id
+     *
+     * @param id the id of the DataMap
+     * @return the DataMap
+     */
     public @Nullable DataMap getDataMap(int id) {
         return mDataMapMap.get(id);
     }
@@ -526,15 +557,32 @@
         return mCollectionMap.get(id & 0xFFFFF).getLength();
     }
 
+    /**
+     * sets the RemoteContext
+     *
+     * @param context the context
+     */
     public void setContext(@NonNull RemoteContext context) {
         mRemoteContext = context;
         mRemoteContext.clearLastOpCount();
     }
 
+    /**
+     * Add an object to the cache. Uses the id for the item and adds it to the cache based
+     *
+     * @param id the id of the object
+     * @param value the object
+     */
     public void updateObject(int id, @NonNull Object value) {
         mObjectMap.put(id, value);
     }
 
+    /**
+     * Get an object from the cache
+     *
+     * @param id The id of the object
+     * @return The object
+     */
     public @Nullable Object getObject(int id) {
         return mObjectMap.get(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 23c3628..36e4ec1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -47,7 +47,7 @@
             new RemoteComposeState(); // todo, is this a valid use of RemoteComposeState -- bbade@
 
     @Nullable protected PaintContext mPaintContext = null;
-    protected float mDensity = 2.75f;
+    protected float mDensity = Float.NaN;
 
     @NonNull ContextMode mMode = ContextMode.UNSET;
 
@@ -77,7 +77,7 @@
      * @param density
      */
     public void setDensity(float density) {
-        if (density > 0) {
+        if (!Float.isNaN(density) && density > 0) {
             mDensity = density;
         }
     }
@@ -234,23 +234,60 @@
      */
     public abstract void addCollection(int id, @NonNull ArrayAccess collection);
 
+    /**
+     * put DataMap under an id
+     *
+     * @param id the id of the DataMap
+     * @param map the DataMap
+     */
     public abstract void putDataMap(int id, @NonNull DataMap map);
 
+    /**
+     * Get a DataMap given an id
+     *
+     * @param id the id of the DataMap
+     * @return the DataMap
+     */
     public abstract @Nullable DataMap getDataMap(int id);
 
+    /**
+     * Run an action
+     *
+     * @param id the id of the action
+     * @param metadata the metadata of the action
+     */
     public abstract void runAction(int id, @NonNull String metadata);
 
     // TODO: we might add an interface to group all valid parameter types
+
+    /**
+     * Run an action with a named parameter
+     *
+     * @param textId the text id of the action
+     * @param value the value of the parameter
+     */
     public abstract void runNamedAction(int textId, Object value);
 
+    /**
+     * Put an object under an id
+     *
+     * @param mId the id of the object
+     * @param command the object
+     */
     public abstract void putObject(int mId, @NonNull Object command);
 
+    /**
+     * Get an object given an id
+     *
+     * @param mId the id of the object
+     * @return the object
+     */
     public abstract @Nullable Object getObject(int mId);
 
     /**
      * Add a touch listener to the context
      *
-     * @param touchExpression
+     * @param touchExpression the touch expression
      */
     public void addTouchListener(TouchListener touchExpression) {}
 
@@ -668,11 +705,24 @@
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Click handling
     ///////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Is this a time id float
+     *
+     * @param fl the floatId to test
+     * @return true if it is a time id
+     */
     public static boolean isTime(float fl) {
         int value = Utils.idFromNan(fl);
         return value >= ID_CONTINUOUS_SEC && value <= ID_DAY_OF_MONTH;
     }
 
+    /**
+     * get the time from a float id that indicates a type of time
+     *
+     * @param fl id of the type of time information requested
+     * @return various time information such as seconds or min
+     */
     public static float getTime(float fl) {
         LocalDateTime dateTime =
                 LocalDateTime.now(ZoneId.systemDefault()); // TODO, pass in a timezone explicitly?
@@ -716,6 +766,17 @@
         return fl;
     }
 
+    /**
+     * Add a click area to the doc
+     *
+     * @param id the id of the click area
+     * @param contentDescription the content description of the click area
+     * @param left the left bounds of the click area
+     * @param top the top bounds of the click area
+     * @param right the right bounds of the click area
+     * @param bottom the
+     * @param metadataId the id of the metadata string
+     */
     public abstract void addClickArea(
             int id,
             int contentDescription,
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 b55f25c..06ef997 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
@@ -102,6 +102,12 @@
         return OP_CODE;
     }
 
+    /**
+     * Apply this operation to the buffer
+     *
+     * @param buffer the buffer to apply the operation to
+     * @param id the id of the path
+     */
     public static void apply(@NonNull WireBuffer buffer, int id) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
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 ac6271c..7a72b10 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
@@ -70,6 +70,13 @@
         return "DataListFloat[" + Utils.idString(mId) + "] " + Arrays.toString(mValues);
     }
 
+    /**
+     * Write this operation to the buffer
+     *
+     * @param buffer the buffer to apply the operation to
+     * @param id the id of the array
+     * @param values the values of the array
+     */
     public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] values) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
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 47cbff3..7e29620 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
@@ -62,6 +62,13 @@
         return "map[" + Utils.idString(mId) + "]  \"" + Arrays.toString(mIds) + "\"";
     }
 
+    /**
+     * Write this operation to the buffer
+     *
+     * @param buffer the buffer to apply the operation to
+     * @param id the id of the array
+     * @param ids the values of the array
+     */
     public static void apply(@NonNull WireBuffer buffer, int id, @NonNull int[] ids) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
index ff85721..33752e0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
@@ -89,6 +89,15 @@
         return builder.toString();
     }
 
+    /**
+     * Write this operation to the buffer
+     *
+     * @param buffer the buffer to apply the operation to
+     * @param id the id
+     * @param names the names of the variables
+     * @param type the types of the variables
+     * @param ids the ids of the variables
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int id,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
index c1e2e66..7f1ba6f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
@@ -31,8 +31,8 @@
 /** Base class for commands that take 3 float */
 public abstract class DrawBase2 extends PaintOperation implements VariableSupport {
     @NonNull protected String mName = "DrawRectBase";
-    float mV1;
-    float mV2;
+    protected float mV1;
+    protected float mV2;
     float mValue1;
     float mValue2;
 
@@ -76,6 +76,13 @@
         return mName + " " + floatToString(mV1) + " " + floatToString(mV2);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param maker the maker of the operation
+     * @param buffer the buffer to read
+     * @param operations the list of operations to add to
+     */
     public static void read(
             @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float v1 = buffer.readFloat();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
index 6fedea3..a6bfda8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
@@ -92,6 +92,13 @@
                 + floatToString(mV3);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param maker the maker of the operation
+     * @param buffer the buffer to read
+     * @param operations the list of operations to add to
+     */
     public static void read(
             @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float v1 = buffer.readFloat();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
index aa9cc68..1e96bcd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
@@ -102,6 +102,13 @@
                 + floatToString(mY2Value, mY2);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param maker the maker of the operation
+     * @param buffer the buffer to read
+     * @param operations the list of operations to add to
+     */
     public static void read(
             @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float v1 = buffer.readFloat();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
index 64c2730..bc59045 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
@@ -116,6 +116,13 @@
         DrawBase6 create(float v1, float v2, float v3, float v4, float v5, float v6);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param build interface to construct the component
+     * @param buffer the buffer to read from
+     * @param operations the list of operations to add to
+     */
     public static void read(
             @NonNull Maker build, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float sv1 = buffer.readFloat();
@@ -132,13 +139,13 @@
     /**
      * writes out a the operation to the buffer.
      *
-     * @param v1
-     * @param v2
-     * @param v3
-     * @param v4
-     * @param v5
-     * @param v6
-     * @return
+     * @param v1 the first parameter
+     * @param v2 the second parameter
+     * @param v3 the third parameter
+     * @param v4 the fourth parameter
+     * @param v5 the fifth parameter
+     * @param v6 the sixth parameter
+     * @return the operation
      */
     @Nullable
     public Operation construct(float v1, float v2, float v3, float v4, float v5, float v6) {
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 cdb527d..40d3bed 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
@@ -137,6 +137,17 @@
         return OP_CODE;
     }
 
+    /**
+     * Writes out the operation to the buffer
+     *
+     * @param buffer the buffer to write to
+     * @param id the id of the Bitmap
+     * @param left left most x coordinate
+     * @param top top most y coordinate
+     * @param right right most x coordinate
+     * @param bottom bottom most y coordinate
+     * @param descriptionId string id of the description
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int id,
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 638fe14..013dd1a 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
@@ -131,6 +131,21 @@
         return OP_CODE;
     }
 
+    /**
+     * Draw a bitmap using integer coordinates
+     *
+     * @param buffer the buffer to write to
+     * @param imageId the id of the bitmap
+     * @param srcLeft the left most pixel in the bitmap
+     * @param srcTop the top most pixel in the bitmap
+     * @param srcRight the right most pixel in the bitmap
+     * @param srcBottom the bottom most pixel in the bitmap
+     * @param dstLeft the left most pixel in the destination
+     * @param dstTop the top most pixel in the destination
+     * @param dstRight the right most pixel in the destination
+     * @param dstBottom the bottom most pixel in the destination
+     * @param cdId the content discription id
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int imageId,
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 d6467c9..e1070f9 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
@@ -217,6 +217,23 @@
         return OP_CODE;
     }
 
+    /**
+     * Draw a bitmap using integer coordinates
+     *
+     * @param buffer the buffer to write to
+     * @param imageId the id of the image
+     * @param srcLeft the left most pixel in the image to draw
+     * @param srcTop the right most pixel in the image to draw
+     * @param srcRight the right most pixel in the image to draw
+     * @param srcBottom the bottom most pixel in the image to draw
+     * @param dstLeft the left most pixel in the destination
+     * @param dstTop the top most pixel in the destination
+     * @param dstRight the right most pixel in the destination
+     * @param dstBottom the bottom most pixel in the destination
+     * @param scaleType the type of scale operation
+     * @param scaleFactor the scalefactor to use with fixed scale
+     * @param cdId the content discription id
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int imageId,
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 398cf48..db9c4d3 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
@@ -81,6 +81,12 @@
         return Operations.DRAW_PATH;
     }
 
+    /**
+     * Draw a path
+     *
+     * @param buffer the buffer to write to
+     * @param id the id of the path
+     */
     public static void apply(@NonNull WireBuffer buffer, int id) {
         buffer.start(Operations.DRAW_PATH);
         buffer.writeInt(id);
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 86f3c99..3ab4a87 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
@@ -117,6 +117,15 @@
         return Operations.DRAW_TEXT_ON_PATH;
     }
 
+    /**
+     * add a draw text on path operation to the buffer
+     *
+     * @param buffer the buffer to add to
+     * @param textId the id of the text string
+     * @param pathId the id of the path
+     * @param hOffset the horizontal offset to position the string
+     * @param vOffset the vertical offset to position the string
+     */
     public static void apply(
             @NonNull WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) {
         buffer.start(OP_CODE);
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 d4d4a5e..e288394 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
@@ -127,6 +127,16 @@
         return Operations.DRAW_TWEEN_PATH;
     }
 
+    /**
+     * add a draw tween path operation to the buffer
+     *
+     * @param buffer the buffer to add to
+     * @param path1Id the first path
+     * @param path2Id the second path
+     * @param tween the amount of the tween
+     * @param start the start sub range to draw
+     * @param stop the end of the sub range to draw
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int path1Id,
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 044430d..66daa13 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
@@ -74,6 +74,11 @@
         return OP_CODE;
     }
 
+    /**
+     * add a matrix restore operation to the buffer
+     *
+     * @param buffer the buffer to add to
+     */
     public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(OP_CODE);
     }
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 aec316a..ec918e8 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
@@ -72,6 +72,11 @@
         return OP_CODE;
     }
 
+    /**
+     * add a matrix save operation to the buffer
+     *
+     * @param buffer the buffer to add to
+     */
     public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(Operations.MATRIX_SAVE);
     }
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 daf2c55..f756b76 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
@@ -32,6 +32,7 @@
 
 import java.util.List;
 
+/** Paint data operation */
 public class PaintData extends PaintOperation implements VariableSupport {
     private static final int OP_CODE = Operations.PAINT_VALUES;
     private static final String CLASS_NAME = "PaintData";
@@ -80,6 +81,12 @@
         return OP_CODE;
     }
 
+    /**
+     * add a paint data to the buffer
+     *
+     * @param buffer the buffer to add to
+     * @param paintBundle the paint bundle
+     */
     public static void apply(@NonNull WireBuffer buffer, @NonNull PaintBundle paintBundle) {
         buffer.start(Operations.PAINT_VALUES);
         paintBundle.writeBundle(buffer);
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 7ff879e..e7cce03 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
@@ -101,6 +101,7 @@
     public static final int CUBIC = 14;
     public static final int CLOSE = 15;
     public static final int DONE = 16;
+    public static final int RESET = 17;
     public static final float MOVE_NAN = Utils.asNan(MOVE);
     public static final float LINE_NAN = Utils.asNan(LINE);
     public static final float QUADRATIC_NAN = Utils.asNan(QUADRATIC);
@@ -108,6 +109,7 @@
     public static final float CUBIC_NAN = Utils.asNan(CUBIC);
     public static final float CLOSE_NAN = Utils.asNan(CLOSE);
     public static final float DONE_NAN = Utils.asNan(DONE);
+    public static final float RESET_NAN = Utils.asNan(RESET);
 
     /**
      * The name of the class
@@ -128,6 +130,14 @@
         return OP_CODE;
     }
 
+    /**
+     * add a path append operation to the buffer. With PathCreate allows you create a path
+     * dynamically
+     *
+     * @param buffer add the data to this buffer
+     * @param id id of the path
+     * @param data the path data to append
+     */
     public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
@@ -175,6 +185,10 @@
     public void apply(@NonNull RemoteContext context) {
         float[] data = context.getPathData(mInstanceId);
         float[] out = mOutputPath;
+        if (Float.floatToRawIntBits(out[0]) == Float.floatToRawIntBits(RESET_NAN)) {
+            context.loadPathData(mInstanceId, new float[0]);
+            return;
+        }
         if (data != null) {
             out = new float[data.length + mOutputPath.length];
 
@@ -190,6 +204,12 @@
         context.loadPathData(mInstanceId, out);
     }
 
+    /**
+     * Convert a path to a string
+     *
+     * @param path the path to convert
+     * @return text representation of path
+     */
     @NonNull
     public static String pathString(@Nullable float[] path) {
         if (path == null) {
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 75562cd..1f76639 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
@@ -131,6 +131,14 @@
         return OP_CODE;
     }
 
+    /**
+     * add a create path operation
+     *
+     * @param buffer buffer to add to
+     * @param id the id of the path
+     * @param startX the start x of the path (moveTo x,y)
+     * @param startY the start y of the path (moveTo x,y)
+     */
     public static void apply(@NonNull WireBuffer buffer, int id, float startX, float startY) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
@@ -165,6 +173,12 @@
                 .field(FLOAT, "startX", "initial start y");
     }
 
+    /**
+     * convert a path to a string
+     *
+     * @param path path to convert (expressed as an array of floats)
+     * @return the text representing the path
+     */
     @NonNull
     public static String pathString(@Nullable float[] path) {
         if (path == null) {
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 85a01fc..45d99a7 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
@@ -131,6 +131,13 @@
         return OP_CODE;
     }
 
+    /**
+     * add a create path operation
+     *
+     * @param buffer buffer to add to
+     * @param id the id of the path
+     * @param data the path
+     */
     public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) {
         buffer.start(Operations.DATA_PATH);
         buffer.writeInt(id);
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 d48de37..5788d8f 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
@@ -73,6 +73,13 @@
         return OP_CODE;
     }
 
+    /**
+     * add a text data operation
+     *
+     * @param buffer buffer to add to
+     * @param textId the id for the text
+     * @param text the data to encode
+     */
     public static void apply(@NonNull WireBuffer buffer, int textId, @NonNull String text) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
index 37ea567..a6570a3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
@@ -50,6 +50,11 @@
         return CLASS_NAME + "[" + mLengthId + "] = " + mTextId;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     public static @NonNull String name() {
         return CLASS_NAME;
     }
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 d51b389..58cd68e 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
@@ -66,6 +66,11 @@
         return "FloatConstant[" + mId + "] = " + mTextId + " " + mType;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     public static @NonNull String name() {
         return CLASS_NAME;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
index e71cb9a..dcd3348 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
@@ -36,10 +36,12 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
+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.ModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
+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.operations.layout.modifiers.ZIndexModifierOperation;
 
@@ -204,6 +206,9 @@
         mPaddingRight = 0f;
         mPaddingBottom = 0f;
 
+        WidthInModifierOperation widthInConstraints = null;
+        HeightInModifierOperation heightInConstraints = null;
+
         for (OperationInterface op : mComponentModifiers.getList()) {
             if (op instanceof PaddingModifierOperation) {
                 // We are accumulating padding modifiers to compute the margin
@@ -221,6 +226,10 @@
                 mWidthModifier = (WidthModifierOperation) op;
             } else if (op instanceof HeightModifierOperation && mHeightModifier == null) {
                 mHeightModifier = (HeightModifierOperation) op;
+            } else if (op instanceof WidthInModifierOperation) {
+                widthInConstraints = (WidthInModifierOperation) op;
+            } else if (op instanceof HeightInModifierOperation) {
+                heightInConstraints = (HeightInModifierOperation) op;
             } else if (op instanceof ZIndexModifierOperation) {
                 mZIndexModifier = (ZIndexModifierOperation) op;
             } else if (op instanceof GraphicsLayerModifierOperation) {
@@ -241,6 +250,12 @@
         if (mHeightModifier == null) {
             mHeightModifier = new HeightModifierOperation(DimensionModifierOperation.Type.WRAP);
         }
+        if (widthInConstraints != null) {
+            mWidthModifier.setWidthIn(widthInConstraints);
+        }
+        if (heightInConstraints != null) {
+            mHeightModifier.setHeightIn(heightInConstraints);
+        }
         setWidth(computeModifierDefinedWidth(null));
         setHeight(computeModifierDefinedHeight(null));
     }
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
new file mode 100644
index 0000000..afc41b1
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
@@ -0,0 +1,175 @@
+/*
+ * 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 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.operations.layout.Component;
+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 java.util.List;
+
+public class CollapsibleColumnLayout extends ColumnLayout {
+
+    public CollapsibleColumnLayout(
+            @Nullable Component parent,
+            int componentId,
+            int animationId,
+            float x,
+            float y,
+            float width,
+            float height,
+            int horizontalPositioning,
+            int verticalPositioning,
+            float spacedBy) {
+        super(
+                parent,
+                componentId,
+                animationId,
+                x,
+                y,
+                width,
+                height,
+                horizontalPositioning,
+                verticalPositioning,
+                spacedBy);
+    }
+
+    public CollapsibleColumnLayout(
+            @Nullable Component parent,
+            int componentId,
+            int animationId,
+            int horizontalPositioning,
+            int verticalPositioning,
+            float spacedBy) {
+        super(
+                parent,
+                componentId,
+                animationId,
+                horizontalPositioning,
+                verticalPositioning,
+                spacedBy);
+    }
+
+    @NonNull
+    @Override
+    protected String getSerializedName() {
+        return "COLLAPSIBLE_COLUMN";
+    }
+
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
+    public static int id() {
+        return Operations.LAYOUT_COLLAPSIBLE_COLUMN;
+    }
+
+    /**
+     * Write the operation to the buffer
+     *
+     * @param buffer wire buffer
+     * @param componentId component id
+     * @param animationId animation id (-1 if not set)
+     * @param horizontalPositioning horizontal positioning rules
+     * @param verticalPositioning vertical positioning rules
+     * @param spacedBy spaced by value
+     */
+    public static void apply(
+            @NonNull WireBuffer buffer,
+            int componentId,
+            int animationId,
+            int horizontalPositioning,
+            int verticalPositioning,
+            float spacedBy) {
+        buffer.start(id());
+        buffer.writeInt(componentId);
+        buffer.writeInt(animationId);
+        buffer.writeInt(horizontalPositioning);
+        buffer.writeInt(verticalPositioning);
+        buffer.writeFloat(spacedBy);
+    }
+
+    /**
+     * 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();
+        float spacedBy = buffer.readFloat();
+        operations.add(
+                new CollapsibleColumnLayout(
+                        null,
+                        componentId,
+                        animationId,
+                        horizontalPositioning,
+                        verticalPositioning,
+                        spacedBy));
+    }
+
+    @Override
+    protected boolean hasVerticalIntrinsicDimension() {
+        return true;
+    }
+
+    @Override
+    public void computeWrapSize(
+            @NonNull PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            boolean horizontalWrap,
+            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;
+            } else {
+                childrenHeight += childMeasure.getH();
+                childrenWidth = Math.max(childrenWidth, childMeasure.getW());
+            }
+        }
+        return changedVisibility;
+    }
+}
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
new file mode 100644
index 0000000..0e7eb86
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
@@ -0,0 +1,175 @@
+/*
+ * 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 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.operations.layout.Component;
+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 java.util.List;
+
+public class CollapsibleRowLayout extends RowLayout {
+
+    public CollapsibleRowLayout(
+            @Nullable Component parent,
+            int componentId,
+            int animationId,
+            float x,
+            float y,
+            float width,
+            float height,
+            int horizontalPositioning,
+            int verticalPositioning,
+            float spacedBy) {
+        super(
+                parent,
+                componentId,
+                animationId,
+                x,
+                y,
+                width,
+                height,
+                horizontalPositioning,
+                verticalPositioning,
+                spacedBy);
+    }
+
+    public CollapsibleRowLayout(
+            @Nullable Component parent,
+            int componentId,
+            int animationId,
+            int horizontalPositioning,
+            int verticalPositioning,
+            float spacedBy) {
+        super(
+                parent,
+                componentId,
+                animationId,
+                horizontalPositioning,
+                verticalPositioning,
+                spacedBy);
+    }
+
+    @NonNull
+    @Override
+    protected String getSerializedName() {
+        return "COLLAPSIBLE_ROW";
+    }
+
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
+    public static int id() {
+        return Operations.LAYOUT_COLLAPSIBLE_ROW;
+    }
+
+    /**
+     * Write the operation to the buffer
+     *
+     * @param buffer wire buffer
+     * @param componentId component id
+     * @param animationId animation id (-1 if not set)
+     * @param horizontalPositioning horizontal positioning rules
+     * @param verticalPositioning vertical positioning rules
+     * @param spacedBy spaced by value
+     */
+    public static void apply(
+            @NonNull WireBuffer buffer,
+            int componentId,
+            int animationId,
+            int horizontalPositioning,
+            int verticalPositioning,
+            float spacedBy) {
+        buffer.start(id());
+        buffer.writeInt(componentId);
+        buffer.writeInt(animationId);
+        buffer.writeInt(horizontalPositioning);
+        buffer.writeInt(verticalPositioning);
+        buffer.writeFloat(spacedBy);
+    }
+
+    /**
+     * 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();
+        float spacedBy = buffer.readFloat();
+        operations.add(
+                new CollapsibleRowLayout(
+                        null,
+                        componentId,
+                        animationId,
+                        horizontalPositioning,
+                        verticalPositioning,
+                        spacedBy));
+    }
+
+    @Override
+    protected boolean hasHorizontalIntrinsicDimension() {
+        return true;
+    }
+
+    @Override
+    public void computeWrapSize(
+            @NonNull PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            boolean horizontalWrap,
+            boolean verticalWrap,
+            @NonNull MeasurePass measure,
+            @NonNull Size size) {
+        super.computeWrapSize(
+                context, Float.MAX_VALUE, maxHeight, 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 (childrenWidth + childMeasure.getW() > selfWidth) {
+                childMeasure.setVisibility(Visibility.GONE);
+                changedVisibility = true;
+            } else {
+                childrenWidth += childMeasure.getW();
+                childrenHeight = Math.max(childrenHeight, childMeasure.getH());
+            }
+        }
+        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 f68d7b4..4d0cbef 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
@@ -32,6 +32,7 @@
 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.utils.DebugLog;
 
 import java.util.List;
@@ -93,7 +94,8 @@
     @NonNull
     @Override
     public String toString() {
-        return "COLUMN ["
+        return getSerializedName()
+                + " ["
                 + mComponentId
                 + ":"
                 + mAnimationId
@@ -213,41 +215,62 @@
             selfHeight =
                     mComponentModifiers.getVerticalScrollDimension() - mPaddingTop - mPaddingBottom;
         }
-        boolean hasWeights = false;
-        float totalWeights = 0f;
-        for (Component child : mChildrenComponents) {
-            ComponentMeasure childMeasure = measure.get(child);
-            if (childMeasure.getVisibility() == Visibility.GONE) {
-                continue;
-            }
-            if (child instanceof LayoutComponent
-                    && ((LayoutComponent) child).getHeightModifier().hasWeight()) {
-                hasWeights = true;
-                totalWeights += ((LayoutComponent) child).getHeightModifier().getValue();
-            } else {
-                childrenHeight += childMeasure.getH();
-            }
-        }
-        if (hasWeights) {
-            float availableSpace = selfHeight - childrenHeight;
+        boolean checkWeights = true;
+        while (checkWeights) {
+            checkWeights = false;
+            boolean hasWeights = false;
+            float totalWeights = 0f;
             for (Component child : mChildrenComponents) {
+                ComponentMeasure childMeasure = measure.get(child);
+                if (childMeasure.getVisibility() == Visibility.GONE) {
+                    continue;
+                }
                 if (child instanceof LayoutComponent
                         && ((LayoutComponent) child).getHeightModifier().hasWeight()) {
-                    ComponentMeasure childMeasure = measure.get(child);
-                    if (childMeasure.getVisibility() == Visibility.GONE) {
-                        continue;
-                    }
-                    float weight = ((LayoutComponent) child).getHeightModifier().getValue();
-                    childMeasure.setH((weight * availableSpace) / totalWeights);
-                    child.measure(
-                            context,
-                            childMeasure.getW(),
-                            childMeasure.getW(),
-                            childMeasure.getH(),
-                            childMeasure.getH(),
-                            measure);
+                    hasWeights = true;
+                    totalWeights += ((LayoutComponent) child).getHeightModifier().getValue();
+                } else {
+                    childrenHeight += childMeasure.getH();
                 }
             }
+            if (hasWeights) {
+                float availableSpace = selfHeight - childrenHeight;
+                for (Component child : mChildrenComponents) {
+                    if (child instanceof LayoutComponent
+                            && ((LayoutComponent) child).getHeightModifier().hasWeight()) {
+                        ComponentMeasure childMeasure = measure.get(child);
+                        if (childMeasure.getVisibility() == Visibility.GONE) {
+                            continue;
+                        }
+                        float weight = ((LayoutComponent) child).getHeightModifier().getValue();
+                        float childHeight = (weight * availableSpace) / totalWeights;
+                        HeightInModifierOperation heightInConstraints =
+                                ((LayoutComponent) child).getHeightModifier().getHeightIn();
+                        if (heightInConstraints != null) {
+                            float min = heightInConstraints.getMin();
+                            float max = heightInConstraints.getMax();
+                            if (min != -1) {
+                                childHeight = Math.max(min, childHeight);
+                            }
+                            if (max != -1) {
+                                childHeight = Math.min(max, childHeight);
+                            }
+                        }
+                        childMeasure.setH(childHeight);
+                        child.measure(
+                                context,
+                                childMeasure.getW(),
+                                childMeasure.getW(),
+                                childMeasure.getH(),
+                                childMeasure.getH(),
+                                measure);
+                    }
+                }
+            }
+
+            if (applyVisibility(selfWidth, selfHeight, measure) && hasWeights) {
+                checkWeights = true;
+            }
         }
 
         childrenHeight = 0f;
@@ -360,6 +383,16 @@
         return Operations.LAYOUT_COLUMN;
     }
 
+    /**
+     * Write the operation to the buffer
+     *
+     * @param buffer wire buffer
+     * @param componentId component id
+     * @param animationId animation id (-1 if not set)
+     * @param horizontalPositioning horizontal positioning rules
+     * @param verticalPositioning vertical positioning rules
+     * @param spacedBy spaced by value
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int componentId,
@@ -367,7 +400,7 @@
             int horizontalPositioning,
             int verticalPositioning,
             float spacedBy) {
-        buffer.start(Operations.LAYOUT_COLUMN);
+        buffer.start(id());
         buffer.writeInt(componentId);
         buffer.writeInt(animationId);
         buffer.writeInt(horizontalPositioning);
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 edfd69c..8b52bbe 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
@@ -43,6 +43,18 @@
         super(parent, componentId, animationId, x, y, width, height);
     }
 
+    /**
+     * Allows layout managers to override elements visibility
+     *
+     * @param selfWidth intrinsic width of the layout manager content
+     * @param selfHeight intrinsic height of the layout manager content
+     * @param measure measure pass
+     */
+    public boolean applyVisibility(
+            float selfWidth, float selfHeight, @NonNull MeasurePass measure) {
+        return false;
+    }
+
     /** Implemented by subclasses to provide a layout/measure pass */
     public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
         // nothing here
@@ -197,7 +209,7 @@
         }
 
         if (!hasWrap) {
-            if (hasHorizontalScroll()) {
+            if (hasHorizontalIntrinsicDimension()) {
                 mCachedWrapSize.setWidth(0f);
                 mCachedWrapSize.setHeight(0f);
                 computeWrapSize(
@@ -210,15 +222,19 @@
                         mCachedWrapSize);
                 float w = mCachedWrapSize.getWidth();
                 computeSize(context, 0f, w, 0, measuredHeight, measure);
-                mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w);
-            } else if (hasVerticalScroll()) {
+                if (hasHorizontalScroll()) {
+                    mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w);
+                }
+            } else if (hasVerticalIntrinsicDimension()) {
                 mCachedWrapSize.setWidth(0f);
                 mCachedWrapSize.setHeight(0f);
                 computeWrapSize(
                         context, maxWidth, Float.MAX_VALUE, false, false, measure, mCachedWrapSize);
                 float h = mCachedWrapSize.getHeight();
                 computeSize(context, 0f, measuredWidth, 0, h, measure);
-                mComponentModifiers.setVerticalScrollDimension(measuredHeight, h);
+                if (hasVerticalScroll()) {
+                    mComponentModifiers.setVerticalScrollDimension(measuredHeight, h);
+                }
             } else {
                 float maxChildWidth = measuredWidth - mPaddingLeft - mPaddingRight;
                 float maxChildHeight = measuredHeight - mPaddingTop - mPaddingBottom;
@@ -246,6 +262,14 @@
         return mComponentModifiers.hasHorizontalScroll();
     }
 
+    protected boolean hasHorizontalIntrinsicDimension() {
+        return hasHorizontalScroll();
+    }
+
+    protected boolean hasVerticalIntrinsicDimension() {
+        return hasVerticalScroll();
+    }
+
     private boolean hasVerticalScroll() {
         return mComponentModifiers.hasVerticalScroll();
     }
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 b688f6e..5b35c4c 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
@@ -32,6 +32,7 @@
 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.WidthInModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog;
 
 import java.util.List;
@@ -91,7 +92,8 @@
     @NonNull
     @Override
     public String toString() {
-        return "ROW ["
+        return getSerializedName()
+                + " ["
                 + mComponentId
                 + ":"
                 + mAnimationId
@@ -212,44 +214,66 @@
                     mComponentModifiers.getVerticalScrollDimension() - mPaddingTop - mPaddingBottom;
         }
 
-        boolean hasWeights = false;
-        float totalWeights = 0f;
-        for (Component child : mChildrenComponents) {
-            ComponentMeasure childMeasure = measure.get(child);
-            if (childMeasure.getVisibility() == Visibility.GONE) {
-                continue;
-            }
-            if (child instanceof LayoutComponent
-                    && ((LayoutComponent) child).getWidthModifier().hasWeight()) {
-                hasWeights = true;
-                totalWeights += ((LayoutComponent) child).getWidthModifier().getValue();
-            } else {
-                childrenWidth += childMeasure.getW();
-            }
-        }
+        boolean checkWeights = true;
 
-        // TODO: need to move the weight measuring in the measure function,
-        // currently we'll measure unnecessarily
-        if (hasWeights) {
-            float availableSpace = selfWidth - childrenWidth;
+        while (checkWeights) {
+            checkWeights = false;
+            boolean hasWeights = false;
+            float totalWeights = 0f;
             for (Component child : mChildrenComponents) {
+                ComponentMeasure childMeasure = measure.get(child);
+                if (childMeasure.getVisibility() == Visibility.GONE) {
+                    continue;
+                }
                 if (child instanceof LayoutComponent
                         && ((LayoutComponent) child).getWidthModifier().hasWeight()) {
-                    ComponentMeasure childMeasure = measure.get(child);
-                    if (childMeasure.getVisibility() == Visibility.GONE) {
-                        continue;
-                    }
-                    float weight = ((LayoutComponent) child).getWidthModifier().getValue();
-                    childMeasure.setW((weight * availableSpace) / totalWeights);
-                    child.measure(
-                            context,
-                            childMeasure.getW(),
-                            childMeasure.getW(),
-                            childMeasure.getH(),
-                            childMeasure.getH(),
-                            measure);
+                    hasWeights = true;
+                    totalWeights += ((LayoutComponent) child).getWidthModifier().getValue();
+                } else {
+                    childrenWidth += childMeasure.getW();
                 }
             }
+
+            // TODO: need to move the weight measuring in the measure function,
+            // currently we'll measure unnecessarily
+            if (hasWeights) {
+                float availableSpace = selfWidth - childrenWidth;
+                for (Component child : mChildrenComponents) {
+                    if (child instanceof LayoutComponent
+                            && ((LayoutComponent) child).getWidthModifier().hasWeight()) {
+                        ComponentMeasure childMeasure = measure.get(child);
+                        if (childMeasure.getVisibility() == Visibility.GONE) {
+                            continue;
+                        }
+                        float weight = ((LayoutComponent) child).getWidthModifier().getValue();
+                        float childWidth = (weight * availableSpace) / totalWeights;
+                        WidthInModifierOperation widthInConstraints =
+                                ((LayoutComponent) child).getWidthModifier().getWidthIn();
+                        if (widthInConstraints != null) {
+                            float min = widthInConstraints.getMin();
+                            float max = widthInConstraints.getMax();
+                            if (min != -1) {
+                                childWidth = Math.max(min, childWidth);
+                            }
+                            if (max != -1) {
+                                childWidth = Math.min(max, childWidth);
+                            }
+                        }
+                        childMeasure.setW(childWidth);
+                        child.measure(
+                                context,
+                                childMeasure.getW(),
+                                childMeasure.getW(),
+                                childMeasure.getH(),
+                                childMeasure.getH(),
+                                measure);
+                    }
+                }
+            }
+
+            if (applyVisibility(selfWidth, selfHeight, measure) && hasWeights) {
+                checkWeights = true;
+            }
         }
 
         childrenWidth = 0f;
@@ -363,6 +387,16 @@
         return Operations.LAYOUT_ROW;
     }
 
+    /**
+     * Write the operation to the buffer
+     *
+     * @param buffer wire buffer
+     * @param componentId component id
+     * @param animationId animation id (-1 if not set)
+     * @param horizontalPositioning horizontal positioning rules
+     * @param verticalPositioning vertical positioning rules
+     * @param spacedBy spaced by value
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int componentId,
@@ -370,7 +404,7 @@
             int horizontalPositioning,
             int verticalPositioning,
             float spacedBy) {
-        buffer.start(Operations.LAYOUT_ROW);
+        buffer.start(id());
         buffer.writeInt(componentId);
         buffer.writeInt(animationId);
         buffer.writeInt(horizontalPositioning);
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
new file mode 100644
index 0000000..c19bd2f
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import android.annotation.NonNull;
+
+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.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.operations.DrawBase2;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Set the min / max height dimension on a component */
+public class HeightInModifierOperation extends DrawBase2 implements ModifierOperation {
+    private static final int OP_CODE = Operations.MODIFIER_HEIGHT_IN;
+    public static final String CLASS_NAME = "HeightInModifierOperation";
+
+    /**
+     * 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) {
+        Maker m = HeightInModifierOperation::new;
+        read(m, buffer, operations);
+    }
+
+    /**
+     * Returns the min value
+     *
+     * @return minimum value
+     */
+    public float getMin() {
+        return mV1;
+    }
+
+    /**
+     * Returns the max value
+     *
+     * @return maximum value
+     */
+    public float getMax() {
+        return mV2;
+    }
+
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
+    public static int id() {
+        return OP_CODE;
+    }
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    @Override
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2) {
+        apply(buffer, v1, v2);
+    }
+
+    /**
+     * 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", OP_CODE, "HeightInModifierOperation")
+                .description("Add additional constraints to the height")
+                .field(DocumentedOperation.FLOAT, "min", "The minimum height, -1 if not applied")
+                .field(DocumentedOperation.FLOAT, "max", "The maximum height, -1 if not applied");
+    }
+
+    public HeightInModifierOperation(float min, float max) {
+        super(min, max);
+        mName = CLASS_NAME;
+    }
+
+    @Override
+    public void paint(@NonNull PaintContext context) {}
+
+    /**
+     * Writes out the HeightInModifier to the buffer
+     *
+     * @param buffer buffer to write to
+     * @param x1 start x of DrawOval
+     * @param y1 start y of the DrawOval
+     */
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1) {
+        write(buffer, OP_CODE, x1, y1);
+    }
+
+    @Override
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+        serializer.append(indent, "HEIGHT_IN = [" + getMin() + ", " + getMax() + "]");
+    }
+}
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 ec078a9..4b50a91 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
@@ -31,6 +31,7 @@
 public class HeightModifierOperation extends DimensionModifierOperation {
     private static final int OP_CODE = Operations.MODIFIER_HEIGHT;
     public static final String CLASS_NAME = "HeightModifierOperation";
+    private HeightInModifierOperation mHeightIn = null;
 
     /**
      * The name of the class
@@ -110,4 +111,22 @@
                 .field(INT, "type", "")
                 .field(FLOAT, "value", "");
     }
+
+    /**
+     * Set height in constraints
+     *
+     * @param heightInConstraints height constraints
+     */
+    public void setHeightIn(HeightInModifierOperation heightInConstraints) {
+        mHeightIn = heightInConstraints;
+    }
+
+    /**
+     * Returns height in constraints
+     *
+     * @return height in constraints
+     */
+    public HeightInModifierOperation getHeightIn() {
+        return mHeightIn;
+    }
 }
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
new file mode 100644
index 0000000..c3624e5
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import android.annotation.NonNull;
+
+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.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.operations.DrawBase2;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Set the min / max width dimension on a component */
+public class WidthInModifierOperation extends DrawBase2 implements ModifierOperation {
+    private static final int OP_CODE = Operations.MODIFIER_WIDTH_IN;
+    public static final String CLASS_NAME = "WidthInModifierOperation";
+
+    /**
+     * 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) {
+        Maker m = WidthInModifierOperation::new;
+        read(m, buffer, operations);
+    }
+
+    /**
+     * Returns the min value
+     *
+     * @return minimum value
+     */
+    public float getMin() {
+        return mV1;
+    }
+
+    /**
+     * Returns the max value
+     *
+     * @return maximum value
+     */
+    public float getMax() {
+        return mV2;
+    }
+
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
+    public static int id() {
+        return OP_CODE;
+    }
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    @Override
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2) {
+        apply(buffer, v1, v2);
+    }
+
+    /**
+     * 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", OP_CODE, "WidthInModifierOperation")
+                .description("Add additional constraints to the width")
+                .field(DocumentedOperation.FLOAT, "min", "The minimum width, -1 if not applied")
+                .field(DocumentedOperation.FLOAT, "max", "The maximum width, -1 if not applied");
+    }
+
+    public WidthInModifierOperation(float min, float max) {
+        super(min, max);
+        mName = CLASS_NAME;
+    }
+
+    @Override
+    public void paint(@NonNull PaintContext context) {}
+
+    /**
+     * Writes out the WidthInModifier to the buffer
+     *
+     * @param buffer buffer to write to
+     * @param x1 start x of DrawOval
+     * @param y1 start y of the DrawOval
+     */
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1) {
+        write(buffer, OP_CODE, x1, y1);
+    }
+
+    @Override
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+        serializer.append(indent, "WIDTH_IN = [" + getMin() + ", " + getMax() + "]");
+    }
+}
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 0530598..532027a 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
@@ -31,6 +31,7 @@
 public class WidthModifierOperation extends DimensionModifierOperation {
     private static final int OP_CODE = Operations.MODIFIER_WIDTH;
     public static final String CLASS_NAME = "WidthModifierOperation";
+    private WidthInModifierOperation mWidthIn = null;
 
     /**
      * The name of the class
@@ -110,4 +111,22 @@
                 .field(INT, "type", "")
                 .field(FLOAT, "value", "");
     }
+
+    /**
+     * Set width in constraints
+     *
+     * @param widthInConstraints width constraints
+     */
+    public void setWidthIn(WidthInModifierOperation widthInConstraints) {
+        mWidthIn = widthInConstraints;
+    }
+
+    /**
+     * Returns width in constraints
+     *
+     * @return width in constraints
+     */
+    public WidthInModifierOperation getWidthIn() {
+        return mWidthIn;
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
index b2ea0af..eb834a9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
@@ -150,17 +150,47 @@
     /** RAND_SEED operator */
     public static final float RAND_SEED = asNan(OFFSET + 40);
 
+    /** NOISE_FROM operator calculate a random 0..1 number based on a seed */
+    public static final float NOISE_FROM = asNan(OFFSET + 41);
+
+    /** RANDOM_IN_RANGE random number in range */
+    public static final float RAND_IN_RANGE = asNan(OFFSET + 42);
+
+    /** SQUARE_SUM the sum of the square of two numbers */
+    public static final float SQUARE_SUM = asNan(OFFSET + 43);
+
+    /** STEP x > edge ? 1 : 0; */
+    public static final float STEP = asNan(OFFSET + 44);
+
+    /** SQUARE x*x; */
+    public static final float SQUARE = asNan(OFFSET + 45);
+
+    /** DUP x,x; */
+    public static final float DUP = asNan(OFFSET + 46);
+
+    /** HYPOT sqrt(x*x+y*y); */
+    public static final float HYPOT = asNan(OFFSET + 47);
+
+    /** SWAP y,x; */
+    public static final float SWAP = asNan(OFFSET + 48);
+
+    /** LERP (1-t)*x+t*y; */
+    public static final float LERP = asNan(OFFSET + 49);
+
+    /** SMOOTH_STEP (1-smoothstep(edge0,edge1,x)); */
+    public static final float SMOOTH_STEP = asNan(OFFSET + 50);
+
     /** LAST valid operator */
-    public static final int LAST_OP = OFFSET + 40;
+    public static final int LAST_OP = OFFSET + 50;
 
     /** VAR1 operator */
-    public static final float VAR1 = asNan(OFFSET + 41);
+    public static final float VAR1 = asNan(OFFSET + 51);
 
     /** VAR2 operator */
-    public static final float VAR2 = asNan(OFFSET + 42);
+    public static final float VAR2 = asNan(OFFSET + 52);
 
     /** VAR2 operator */
-    public static final float VAR3 = asNan(OFFSET + 43);
+    public static final float VAR3 = asNan(OFFSET + 53);
 
     // TODO SQUARE, DUP, HYPOT, SWAP
     //    private static final float FP_PI = (float) Math.PI;
@@ -399,6 +429,17 @@
         sNames.put(k++, "RAND");
         sNames.put(k++, "RAND_SEED");
 
+        sNames.put(k++, "noise_from");
+        sNames.put(k++, "rand_in_range");
+        sNames.put(k++, "square_sum");
+        sNames.put(k++, "step");
+        sNames.put(k++, "square");
+        sNames.put(k++, "dup");
+        sNames.put(k++, "hypot");
+        sNames.put(k++, "swap");
+        sNames.put(k++, "lerp");
+        sNames.put(k++, "smooth_step");
+
         sNames.put(k++, "a[0]");
         sNames.put(k++, "a[1]");
         sNames.put(k++, "a[2]");
@@ -615,9 +656,20 @@
     private static final int OP_RAND = OFFSET + 39;
     private static final int OP_RAND_SEED = OFFSET + 40;
 
-    private static final int OP_FIRST_VAR = OFFSET + 41;
-    private static final int OP_SECOND_VAR = OFFSET + 42;
-    private static final int OP_THIRD_VAR = OFFSET + 43;
+    private static final int OP_NOISE_FROM = OFFSET + 41;
+    private static final int OP_RAND_IN_RANGE = OFFSET + 42;
+    private static final int OP_SQUARE_SUM = OFFSET + 43;
+    private static final int OP_STEP = OFFSET + 44;
+    private static final int OP_SQUARE = OFFSET + 45;
+    private static final int OP_DUP = OFFSET + 46;
+    private static final int OP_HYPOT = OFFSET + 47;
+    private static final int OP_SWAP = OFFSET + 48;
+    private static final int OP_LERP = OFFSET + 49;
+    private static final int OP_SMOOTH_STEP = OFFSET + 50;
+
+    private static final int OP_FIRST_VAR = OFFSET + 51;
+    private static final int OP_SECOND_VAR = OFFSET + 52;
+    private static final int OP_THIRD_VAR = OFFSET + 53;
 
     int opEval(int sp, int id) {
         float[] array;
@@ -824,6 +876,66 @@
                     }
                 }
                 return sp - 1;
+            case OP_NOISE_FROM:
+                int x = Float.floatToRawIntBits(mStack[sp]);
+                x = (x << 13) ^ x; // / Bitwise scrambling return
+                mStack[sp] =
+                        (1.0f
+                                - ((x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff)
+                                        / 1073741824.0f);
+                return sp;
+
+            case OP_RAND_IN_RANGE:
+                if (sRandom == null) {
+                    sRandom = new Random();
+                }
+                mStack[sp] = sRandom.nextFloat() * (mStack[sp] - mStack[sp - 1]) + mStack[sp - 1];
+                return sp;
+            case OP_SQUARE_SUM:
+                mStack[sp - 1] = mStack[sp - 1] * mStack[sp - 1] + mStack[sp] * mStack[sp];
+                return sp - 1;
+            case OP_STEP:
+                System.out.println(mStack[sp] + " > " + mStack[sp - 1]);
+                mStack[sp - 1] = (mStack[sp - 1] > mStack[sp]) ? 1f : 0f;
+                return sp - 1;
+            case OP_SQUARE:
+                mStack[sp] = mStack[sp] * mStack[sp];
+                return sp;
+            case OP_DUP:
+                mStack[sp + 1] = mStack[sp];
+                return sp + 1;
+            case OP_HYPOT:
+                mStack[sp - 1] = (float) Math.hypot(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+            case OP_SWAP:
+                float swap = mStack[sp - 1];
+                mStack[sp - 1] = mStack[sp];
+                mStack[sp] = swap;
+                return sp;
+            case OP_LERP:
+                float tmp1 = mStack[sp - 2];
+                float tmp2 = mStack[sp - 1];
+                float tmp3 = mStack[sp];
+                mStack[sp - 2] = tmp1 + (tmp2 - tmp1) * tmp3;
+                return sp - 2;
+            case OP_SMOOTH_STEP:
+                float val3 = mStack[sp - 2];
+                float max2 = mStack[sp - 1];
+                float min1 = mStack[sp];
+                System.out.println("val3 = " + val3 + " min1 = " + min1 + " max2 = " + max2);
+                if (val3 < min1) {
+                    mStack[sp - 2] = 0f;
+                    System.out.println("below min ");
+                } else if (val3 > max2) {
+                    mStack[sp - 2] = 1f;
+                    System.out.println("above max ");
+
+                } else {
+                    float v = (val3 - min1) / (max2 - min1);
+                    System.out.println("v = " + v);
+                    mStack[sp - 2] = v * v * (3 - 2 * v);
+                }
+                return sp - 2;
 
             case OP_FIRST_VAR:
                 mStack[sp] = mVar[0];
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java
index d8bc83e..2b53682 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java
@@ -19,24 +19,58 @@
 public abstract class Easing {
     int mType;
 
-    /** get the value at point x */
+    /**
+     * get the value at point x
+     *
+     * @param x the position at which to get the slope
+     * @return the value at the point
+     */
     public abstract float get(float x);
 
-    /** get the slope of the easing function at at x */
+    /**
+     * get the slope of the easing function at at x
+     *
+     * @param x the position at which to get the slope
+     * @return the slope
+     */
     public abstract float getDiff(float x);
 
+    /**
+     * get the type of easing function
+     *
+     * @return the type of easing function
+     */
     public int getType() {
         return mType;
     }
 
+    /** cubic Easing function that accelerates and decelerates */
     public static final int CUBIC_STANDARD = 1;
+
+    /** cubic Easing function that accelerates */
     public static final int CUBIC_ACCELERATE = 2;
+
+    /** cubic Easing function that decelerates */
     public static final int CUBIC_DECELERATE = 3;
+
+    /** cubic Easing function that just linearly interpolates */
     public static final int CUBIC_LINEAR = 4;
+
+    /** cubic Easing function that goes bacwards and then accelerates */
     public static final int CUBIC_ANTICIPATE = 5;
+
+    /** cubic Easing function that overshoots and then goes back */
     public static final int CUBIC_OVERSHOOT = 6;
+
+    /** cubic Easing function that you customize */
     public static final int CUBIC_CUSTOM = 11;
+
+    /** a monotonic spline Easing function that you customize */
     public static final int SPLINE_CUSTOM = 12;
+
+    /** a bouncing Easing function */
     public static final int EASE_OUT_BOUNCE = 13;
+
+    /** a elastic Easing function */
     public static final int EASE_OUT_ELASTIC = 14;
 }
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 465c95d..65472c2 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
@@ -52,16 +52,25 @@
         return str;
     }
 
-    public FloatAnimation() {
-        mType = CUBIC_STANDARD;
-        mEasingCurve = new CubicEasing(mType);
-    }
-
+    /**
+     * Create an animation based on a float encoding of the animation
+     *
+     * @param description the float encoding of the animation
+     */
     public FloatAnimation(@NonNull float... description) {
         mType = CUBIC_STANDARD;
         setAnimationDescription(description);
     }
 
+    /**
+     * Create an animation based on the parameters
+     *
+     * @param type The type of animation
+     * @param duration The duration of the animation
+     * @param description The float parameters describing the animation
+     * @param initialValue The initial value of the float (NaN if none)
+     * @param wrap The wrap value of the animation NaN if it does not wrap
+     */
     public FloatAnimation(
             int type,
             float duration,
@@ -139,8 +148,8 @@
     /**
      * Useful to debug the packed form of an animation string
      *
-     * @param description
-     * @return
+     * @param description the float encoding of the animation
+     * @return a string describing the animation
      */
     public static String unpackAnimationToString(float[] description) {
         float[] mSpec = description;
@@ -223,7 +232,7 @@
     /**
      * Create an animation based on a float encoding of the animation
      *
-     * @param description
+     * @param description the float encoding of the animation
      */
     public void setAnimationDescription(@NonNull float[] description) {
         mSpec = description;
@@ -288,7 +297,7 @@
     /**
      * Set the initial Value
      *
-     * @param value
+     * @param value the value to set
      */
     public void setInitialValue(float value) {
 
@@ -321,7 +330,7 @@
     /**
      * Set the target value to interpolate to
      *
-     * @param value
+     * @param value the value to set
      */
     public void setTargetValue(float value) {
         mTargetValue = value;
@@ -342,6 +351,11 @@
         setScaleOffset();
     }
 
+    /**
+     * Get the target value
+     *
+     * @return the target value
+     */
     public float getTargetValue() {
         return mTargetValue;
     }
@@ -369,6 +383,11 @@
         return mEasingCurve.getDiff(t / mDuration) * (mTargetValue - mInitialValue);
     }
 
+    /**
+     * Get the initial value
+     *
+     * @return the initial value
+     */
     public float getInitialValue() {
         return mInitialValue;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
index 06969cc..960eff2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
@@ -25,13 +25,18 @@
     /**
      * Set the curve based on the float encoding of it
      *
-     * @param data
+     * @param data the float encoding of the curve
      */
     public void setCurveSpecification(@NonNull float[] data) {
         mEasingData = data;
         createEngine();
     }
 
+    /**
+     * Get the float encoding of the curve
+     *
+     * @return the float encoding of the curve
+     */
     public @NonNull float[] getCurveSpecification() {
         return mEasingData;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
index f4579a2..01d64df 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
@@ -76,10 +76,10 @@
     }
 
     /**
-     * Get the position of all curves at time t
+     * Get the position of all curves at position t
      *
-     * @param t
-     * @param v
+     * @param t the point on the spline
+     * @param v the array to fill (for multiple curves)
      */
     public void getPos(double t, @NonNull double[] v) {
         final int n = mT.length;
@@ -136,10 +136,10 @@
     }
 
     /**
-     * Get the position of all curves at time t
+     * Get the position of all curves at position t
      *
-     * @param t
-     * @param v
+     * @param t the point on the spline
+     * @param v the array to fill
      */
     public void getPos(double t, @NonNull float[] v) {
         final int n = mT.length;
@@ -196,11 +196,11 @@
     }
 
     /**
-     * Get the position of the jth curve at time t
+     * Get the position of the jth curve at position t
      *
-     * @param t
-     * @param j
-     * @return
+     * @param t the position
+     * @param j the curve to get
+     * @return the position
      */
     public double getPos(double t, int j) {
         final int n = mT.length;
@@ -240,8 +240,8 @@
     /**
      * Get the slope of all the curves at position t
      *
-     * @param t
-     * @param v
+     * @param t the position
+     * @param v the array to fill
      */
     public void getSlope(double t, @NonNull double[] v) {
         final int n = mT.length;
@@ -271,9 +271,9 @@
     /**
      * Get the slope of the j curve at position t
      *
-     * @param t
-     * @param j
-     * @return
+     * @param t the position
+     * @param j the curve to get the value at
+     * @return the slope
      */
     public double getSlope(double t, int j) {
         final int n = mT.length;
@@ -297,6 +297,11 @@
         return 0; // should never reach here
     }
 
+    /**
+     * Get the time point used to create the curve
+     *
+     * @return the time points used to create the curve
+     */
     public @NonNull double[] getTimePoints() {
         return mT;
     }
@@ -332,7 +337,12 @@
                 + h * t1;
     }
 
-    /** This builds a monotonic spline to be used as a wave function */
+    /**
+     * This builds a monotonic spline to be used as a wave function
+     *
+     * @param configString the configuration string
+     * @return the curve
+     */
     @NonNull
     public static MonotonicCurveFit buildWave(@NonNull String configString) {
         // done this way for efficiency
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java
index 23a6643..8bb7dae 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java
@@ -76,6 +76,11 @@
         mTangent = tangent;
     }
 
+    /**
+     * Get the value point used in the interpolator.
+     *
+     * @return the value points
+     */
     public float[] getArray() {
         return mY;
     }
@@ -83,7 +88,7 @@
     /**
      * Get the position of all curves at time t
      *
-     * @param t
+     * @param t the position along spline
      * @return position at t
      */
     public float getPos(float t) {
@@ -139,7 +144,7 @@
     /**
      * Get the slope of the curve at position t
      *
-     * @param t
+     * @param t the position along spline
      * @return slope at t
      */
     public float getSlope(float t) {
@@ -167,6 +172,11 @@
         return v;
     }
 
+    /**
+     * Get the time points used in the interpolator.
+     *
+     * @return the time points
+     */
     public float[] getTimePoints() {
         return mT;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java
index 03e4503..2f1379b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java
@@ -42,9 +42,9 @@
     private float mStopThreshold;
     private int mBoundaryMode = 0;
 
-    public String debug(String desc, float time) {
-        return null;
-    }
+    //    public String debug(String desc, float time) {
+    //        return null;
+    //    }
 
     void log(String str) {
         StackTraceElement s = new Throwable().getStackTrace()[1];
@@ -53,20 +53,41 @@
         System.out.println(line + str);
     }
 
+    /** */
     public SpringStopEngine() {}
 
+    /**
+     * get the value the sping is pulling towards
+     *
+     * @return the value the sping is pulling towards
+     */
     public float getTargetValue() {
         return (float) mTargetPos;
     }
 
+    /**
+     * get the value the sping is starting from
+     *
+     * @param v the value the sping is starting from
+     */
     public void setInitialValue(float v) {
         mPos = v;
     }
 
+    /**
+     * set the value the sping is pulling towards
+     *
+     * @param v the value the sping is pulling towards
+     */
     public void setTargetValue(float v) {
         mTargetPos = v;
     }
 
+    /**
+     * Create a sping engine with the parameters encoded as an array of floats
+     *
+     * @param parameters the parameters to use
+     */
     public SpringStopEngine(float[] parameters) {
         if (parameters[0] != 0) {
             throw new RuntimeException(" parameter[0] should be 0");
@@ -83,9 +104,9 @@
     /**
      * Config the spring starting conditions
      *
-     * @param currentPos
-     * @param target
-     * @param currentVelocity
+     * @param currentPos the current position of the spring
+     * @param target the target position of the spring
+     * @param currentVelocity the current velocity of the spring
      */
     public void springStart(float currentPos, float target, float currentVelocity) {
         mTargetPos = target;
@@ -115,10 +136,22 @@
         mLastTime = 0;
     }
 
+    /**
+     * get the velocity of the spring at a time
+     *
+     * @param time the time to get the velocity at
+     * @return the velocity of the spring at a time
+     */
     public float getVelocity(float time) {
         return (float) mV;
     }
 
+    /**
+     * get the position of the spring at a time
+     *
+     * @param time the time to get the position at
+     * @return the position of the spring at a time
+     */
     public float get(float time) {
         compute(time - mLastTime);
         mLastTime = time;
@@ -128,6 +161,11 @@
         return (float) mPos;
     }
 
+    /**
+     * get the acceleration of the spring
+     *
+     * @return the acceleration of the spring
+     */
     public float getAcceleration() {
         double k = mStiffness;
         double c = mDamping;
@@ -135,10 +173,20 @@
         return (float) (-k * x - c * mV) / mMass;
     }
 
+    /**
+     * get the velocity of the spring
+     *
+     * @return the velocity of the spring
+     */
     public float getVelocity() {
         return 0;
     }
 
+    /**
+     * is the spring stopped
+     *
+     * @return true if the spring is stopped
+     */
     public boolean isStopped() {
         double x = (mPos - mTargetPos);
         double k = mStiffness;
@@ -149,6 +197,11 @@
         return max_def <= mStopThreshold;
     }
 
+    /**
+     * increment the spring position over time dt
+     *
+     * @param dt the time to increment the spring position over
+     */
     private void compute(double dt) {
         if (dt <= 0) {
             // Nothing to compute if there's no time difference
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
index b1eb804..376e1e9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
@@ -26,6 +26,13 @@
     //    private static final boolean DEBUG = false;
     @NonNull private final MonotonicCurveFit mCurveFit;
 
+    /**
+     * Create a step curve from a series of values
+     *
+     * @param params the series of values to ease over
+     * @param offset the offset into the array
+     * @param len the length of the array to use
+     */
     public StepCurve(@NonNull float[] params, int offset, int len) {
         mCurveFit = genSpline(params, offset, len);
     }
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 5de11a19..b17e3dc 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -66,6 +66,28 @@
     }
 
     /**
+     * @inheritDoc
+     */
+    public void requestLayout() {
+        super.requestLayout();
+
+        if (mInner != null) {
+            mInner.requestLayout();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public void invalidate() {
+        super.invalidate();
+
+        if (mInner != null) {
+            mInner.invalidate();
+        }
+    }
+
+    /**
      * Returns true if the document supports drag touch events
      *
      * @return true if draggable content, false otherwise
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 970cc4a..334ba62 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
@@ -48,7 +48,7 @@
     boolean mHasClickAreas = false;
     Point mActionDownPoint = new Point(0, 0);
     AndroidRemoteContext mARContext = new AndroidRemoteContext();
-    float mDensity = 1f;
+    float mDensity = Float.NaN;
     long mStart = System.nanoTime();
 
     long mLastFrameDelay = 1;
@@ -68,24 +68,18 @@
 
     public RemoteComposeCanvas(Context context) {
         super(context);
-        if (USE_VIEW_AREA_CLICK) {
-            addOnAttachStateChangeListener(this);
-        }
+        addOnAttachStateChangeListener(this);
     }
 
     public RemoteComposeCanvas(Context context, AttributeSet attrs) {
         super(context, attrs);
-        if (USE_VIEW_AREA_CLICK) {
-            addOnAttachStateChangeListener(this);
-        }
+        addOnAttachStateChangeListener(this);
     }
 
     public RemoteComposeCanvas(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         setBackgroundColor(Color.WHITE);
-        if (USE_VIEW_AREA_CLICK) {
-            addOnAttachStateChangeListener(this);
-        }
+        addOnAttachStateChangeListener(this);
     }
 
     public void setDebug(int value) {
@@ -124,6 +118,7 @@
             mChoreographer.postFrameCallback(mFrameCallback);
         }
         mDensity = getContext().getResources().getDisplayMetrics().density;
+        mARContext.setDensity(mDensity);
         if (mDocument == null) {
             return;
         }
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 7b61a5d..10d6d33 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -107,10 +107,11 @@
         }
     }
 
-    void onWaitForBufferRelease() {
+    void onWaitForBufferRelease(const nsecs_t durationNanos) {
         JNIEnv* env = getenv(mVm);
         getenv(mVm)->CallVoidMethod(mWaitForBufferReleaseObject,
-                                    gWaitForBufferReleaseCallback.onWaitForBufferRelease);
+                                    gWaitForBufferReleaseCallback.onWaitForBufferRelease,
+                                    durationNanos);
         DieIfException(env, "Uncaught exception in WaitForBufferReleaseCallback.");
     }
 
@@ -255,7 +256,9 @@
     } else {
         sp<WaitForBufferReleaseCallbackWrapper> wrapper =
                 new WaitForBufferReleaseCallbackWrapper{env, waitForBufferReleaseCallback};
-        queue->setWaitForBufferReleaseCallback([wrapper]() { wrapper->onWaitForBufferRelease(); });
+        queue->setWaitForBufferReleaseCallback([wrapper](const nsecs_t durationNanos) {
+            wrapper->onWaitForBufferRelease(durationNanos);
+        });
     }
 }
 
@@ -305,7 +308,7 @@
     jclass waitForBufferReleaseClass =
             FindClassOrDie(env, "android/graphics/BLASTBufferQueue$WaitForBufferReleaseCallback");
     gWaitForBufferReleaseCallback.onWaitForBufferRelease =
-            GetMethodIDOrDie(env, waitForBufferReleaseClass, "onWaitForBufferRelease", "()V");
+            GetMethodIDOrDie(env, waitForBufferReleaseClass, "onWaitForBufferRelease", "(J)V");
 
     return 0;
 }
diff --git a/core/jni/android_os_PerfettoTrace.cpp b/core/jni/android_os_PerfettoTrace.cpp
index 988aea7..962aefc 100644
--- a/core/jni/android_os_PerfettoTrace.cpp
+++ b/core/jni/android_os_PerfettoTrace.cpp
@@ -23,6 +23,7 @@
 #include <nativehelper/scoped_local_ref.h>
 #include <nativehelper/scoped_primitive_array.h>
 #include <nativehelper/scoped_utf_chars.h>
+#include <nativehelper/utils.h>
 #include <tracing_sdk.h>
 
 namespace android {
@@ -36,30 +37,6 @@
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(ptr));
 }
 
-static const char* fromJavaString(JNIEnv* env, jstring jstr) {
-    if (!jstr) return "";
-    ScopedUtfChars chars(env, jstr);
-
-    if (!chars.c_str()) {
-        ALOGE("Failed extracting string");
-        return "";
-    }
-
-    return chars.c_str();
-}
-
-static void android_os_PerfettoTrace_event(JNIEnv* env, jclass, jint type, jlong cat_ptr,
-                                           jstring name, jlong extra_ptr) {
-    ScopedUtfChars name_utf(env, name);
-    if (!name_utf.c_str()) {
-        ALOGE("Failed extracting string");
-    }
-
-    tracing_perfetto::Category* category = toPointer<tracing_perfetto::Category>(cat_ptr);
-    tracing_perfetto::trace_event(type, category->get(), name_utf.c_str(),
-                                  toPointer<tracing_perfetto::Extra>(extra_ptr));
-}
-
 static jlong android_os_PerfettoTrace_get_process_track_uuid() {
     return tracing_perfetto::get_process_track_uuid();
 }
@@ -70,20 +47,18 @@
 
 static void android_os_PerfettoTrace_activate_trigger(JNIEnv* env, jclass, jstring name,
                                                       jint ttl_ms) {
-    ScopedUtfChars name_utf(env, name);
-    if (!name_utf.c_str()) {
-        ALOGE("Failed extracting string");
-        return;
-    }
-
-    tracing_perfetto::activate_trigger(name_utf.c_str(), static_cast<uint32_t>(ttl_ms));
+    ScopedUtfChars name_chars = GET_UTF_OR_RETURN_VOID(env, name);
+    tracing_perfetto::activate_trigger(name_chars.c_str(), static_cast<uint32_t>(ttl_ms));
 }
 
 static jlong android_os_PerfettoTraceCategory_init(JNIEnv* env, jclass, jstring name, jstring tag,
                                                    jstring severity) {
-    return toJLong(new tracing_perfetto::Category(fromJavaString(env, name),
-                                                  fromJavaString(env, tag),
-                                                  fromJavaString(env, severity)));
+    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+    ScopedUtfChars tag_chars = GET_UTF_OR_RETURN(env, tag);
+    ScopedUtfChars severity_chars = GET_UTF_OR_RETURN(env, severity);
+
+    return toJLong(new tracing_perfetto::Category(name_chars.c_str(), tag_chars.c_str(),
+                                                  severity_chars.c_str()));
 }
 
 static jlong android_os_PerfettoTraceCategory_delete() {
@@ -121,8 +96,7 @@
 };
 
 static const JNINativeMethod gTraceMethods[] =
-        {{"native_event", "(IJLjava/lang/String;J)V", (void*)android_os_PerfettoTrace_event},
-         {"native_get_process_track_uuid", "()J",
+        {{"native_get_process_track_uuid", "()J",
           (void*)android_os_PerfettoTrace_get_process_track_uuid},
          {"native_get_thread_track_uuid", "(J)J",
           (void*)android_os_PerfettoTrace_get_thread_track_uuid},
@@ -132,10 +106,11 @@
 int register_android_os_PerfettoTrace(JNIEnv* env) {
     int res = jniRegisterNativeMethods(env, "android/os/PerfettoTrace", gTraceMethods,
                                        NELEM(gTraceMethods));
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register perfetto native methods.");
 
     res = jniRegisterNativeMethods(env, "android/os/PerfettoTrace$Category", gCategoryMethods,
                                    NELEM(gCategoryMethods));
-    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register category native methods.");
 
     return 0;
 }
diff --git a/core/jni/android_os_PerfettoTrackEventExtra.cpp b/core/jni/android_os_PerfettoTrackEventExtra.cpp
index 9adad7b..b8bdc8c 100644
--- a/core/jni/android_os_PerfettoTrackEventExtra.cpp
+++ b/core/jni/android_os_PerfettoTrackEventExtra.cpp
@@ -20,6 +20,7 @@
 #include <log/log.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/scoped_utf_chars.h>
+#include <nativehelper/utils.h>
 #include <tracing_sdk.h>
 
 static constexpr ssize_t kMaxStrLen = 4096;
@@ -34,32 +35,24 @@
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(ptr));
 }
 
-static const char* fromJavaString(JNIEnv* env, jstring jstr) {
-    if (!jstr) return "";
-    ScopedUtfChars chars(env, jstr);
-
-    if (!chars.c_str()) {
-        ALOGE("Failed extracting string");
-        return "";
-    }
-
-    return chars.c_str();
-}
-
 static jlong android_os_PerfettoTrackEventExtraArgInt64_init(JNIEnv* env, jclass, jstring name) {
-    return toJLong(new tracing_perfetto::DebugArg<int64_t>(fromJavaString(env, name)));
+    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+    return toJLong(new tracing_perfetto::DebugArg<int64_t>(name_chars.c_str()));
 }
 
 static jlong android_os_PerfettoTrackEventExtraArgBool_init(JNIEnv* env, jclass, jstring name) {
-    return toJLong(new tracing_perfetto::DebugArg<bool>(fromJavaString(env, name)));
+    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+    return toJLong(new tracing_perfetto::DebugArg<bool>(name_chars.c_str()));
 }
 
 static jlong android_os_PerfettoTrackEventExtraArgDouble_init(JNIEnv* env, jclass, jstring name) {
-    return toJLong(new tracing_perfetto::DebugArg<double>(fromJavaString(env, name)));
+    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+    return toJLong(new tracing_perfetto::DebugArg<double>(name_chars.c_str()));
 }
 
 static jlong android_os_PerfettoTrackEventExtraArgString_init(JNIEnv* env, jclass, jstring name) {
-    return toJLong(new tracing_perfetto::DebugArg<const char*>(fromJavaString(env, name)));
+    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+    return toJLong(new tracing_perfetto::DebugArg<const char*>(name_chars.c_str()));
 }
 
 static jlong android_os_PerfettoTrackEventExtraArgInt64_delete() {
@@ -116,9 +109,11 @@
 
 static void android_os_PerfettoTrackEventExtraArgString_set_value(JNIEnv* env, jclass, jlong ptr,
                                                                   jstring val) {
+    ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val);
+
     tracing_perfetto::DebugArg<const char*>* arg =
             toPointer<tracing_perfetto::DebugArg<const char*>>(ptr);
-    arg->set_value(strdup(fromJavaString(env, val)));
+    arg->set_value(strdup(val_chars.c_str()));
 }
 
 static jlong android_os_PerfettoTrackEventExtraFieldInt64_init() {
@@ -191,9 +186,11 @@
 
 static void android_os_PerfettoTrackEventExtraFieldString_set_value(JNIEnv* env, jclass, jlong ptr,
                                                                     jlong id, jstring val) {
+    ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val);
+
     tracing_perfetto::ProtoField<const char*>* field =
             toPointer<tracing_perfetto::ProtoField<const char*>>(ptr);
-    field->set_value(id, strdup(fromJavaString(env, val)));
+    field->set_value(id, strdup(val_chars.c_str()));
 }
 
 static void android_os_PerfettoTrackEventExtraFieldNested_add_field(jlong field_ptr,
@@ -234,7 +231,8 @@
 
 static jlong android_os_PerfettoTrackEventExtraNamedTrack_init(JNIEnv* env, jclass, jlong id,
                                                                jstring name, jlong parent_uuid) {
-    return toJLong(new tracing_perfetto::NamedTrack(id, parent_uuid, fromJavaString(env, name)));
+    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+    return toJLong(new tracing_perfetto::NamedTrack(id, parent_uuid, name_chars.c_str()));
 }
 
 static jlong android_os_PerfettoTrackEventExtraNamedTrack_delete() {
@@ -248,8 +246,9 @@
 
 static jlong android_os_PerfettoTrackEventExtraCounterTrack_init(JNIEnv* env, jclass, jstring name,
                                                                  jlong parent_uuid) {
-    return toJLong(
-            new tracing_perfetto::RegisteredTrack(1, parent_uuid, fromJavaString(env, name), true));
+    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
+
+    return toJLong(new tracing_perfetto::RegisteredTrack(1, parent_uuid, name_chars.c_str(), true));
 }
 
 static jlong android_os_PerfettoTrackEventExtraCounterTrack_delete() {
@@ -317,6 +316,15 @@
     extra->clear_extras();
 }
 
+static void android_os_PerfettoTrackEventExtra_emit(JNIEnv* env, jclass, jint type, jlong cat_ptr,
+                                                    jstring name, jlong extra_ptr) {
+    ScopedUtfChars name_chars = GET_UTF_OR_RETURN_VOID(env, name);
+
+    tracing_perfetto::Category* category = toPointer<tracing_perfetto::Category>(cat_ptr);
+    tracing_perfetto::trace_event(type, category->get(), name_chars.c_str(),
+                                  toPointer<tracing_perfetto::Extra>(extra_ptr));
+}
+
 static jlong android_os_PerfettoTrackEventExtraProto_init() {
     return toJLong(new tracing_perfetto::Proto());
 }
@@ -344,7 +352,9 @@
         {{"native_init", "()J", (void*)android_os_PerfettoTrackEventExtra_init},
          {"native_delete", "()J", (void*)android_os_PerfettoTrackEventExtra_delete},
          {"native_add_arg", "(JJ)V", (void*)android_os_PerfettoTrackEventExtra_add_arg},
-         {"native_clear_args", "(J)V", (void*)android_os_PerfettoTrackEventExtra_clear_args}};
+         {"native_clear_args", "(J)V", (void*)android_os_PerfettoTrackEventExtra_clear_args},
+         {"native_emit", "(IJLjava/lang/String;J)V",
+          (void*)android_os_PerfettoTrackEventExtra_emit}};
 
 static const JNINativeMethod gProtoMethods[] =
         {{"native_init", "()J", (void*)android_os_PerfettoTrackEventExtraProto_init},
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 5d0b340..69c812c 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -109,6 +109,7 @@
         optional SettingProto em_value = 61 [ (android.privacy).dest = DEST_AUTOMATIC ];
         // Settings for accessibility autoclick
         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 Accessibility accessibility = 2;
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 64c9f54..325790c 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -218,7 +218,8 @@
         optional SettingProto tap_to_click = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto tap_dragging = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto three_finger_tap_customization = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
-        optional SettingProto system_gestures = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];;
+        optional SettingProto system_gestures = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto acceleration_enabled = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];;
     }
     optional Touchpad touchpad = 36;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7327970..aad8f8a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8324,16 +8324,15 @@
 
     <!-- Allows an application to perform actions on behalf of users inside of
          applications.
-         <p>This permission is currently only granted to preinstalled / system apps having the
-         {@link android.app.role.ASSISTANT} role.
+         <p>This permission is currently only granted to privileged system apps.
          <p>Apps contributing app functions can opt to disallow callers with this permission,
          limiting to only callers with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}
          instead.
-         <p>Protection level: internal|role
+         <p>Protection level: internal|privileged
          @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)  -->
     <permission android:name="android.permission.EXECUTE_APP_FUNCTIONS"
         android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
-        android:protectionLevel="internal|role" />
+        android:protectionLevel="internal|privileged" />
 
     <!-- Allows an application to display its suggestions using the autofill framework.
          <p>For now, this permission is only granted to the Browser application.
diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml
index 44a5df9..c43975e 100644
--- a/core/res/res/layout/preference_list_fragment.xml
+++ b/core/res/res/layout/preference_list_fragment.xml
@@ -19,7 +19,6 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
-    android:fitsSystemWindows="true"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     android:background="@android:color/transparent"
diff --git a/core/res/res/layout/preference_list_fragment_material.xml b/core/res/res/layout/preference_list_fragment_material.xml
index 4df7602..db2fe7d 100644
--- a/core/res/res/layout/preference_list_fragment_material.xml
+++ b/core/res/res/layout/preference_list_fragment_material.xml
@@ -19,7 +19,6 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
-    android:fitsSystemWindows="true"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     android:background="@android:color/transparent"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index bd1d303..a1f85c3 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7585,7 +7585,7 @@
     <!-- Used to config the segments of a NotificationProgressDrawable. -->
     <!-- @hide internal use only -->
     <declare-styleable name="NotificationProgressDrawableSegments">
-        <!-- TODO: b/372908709 - maybe move this to NotificationProgressBar, because that's the only
+        <!-- TODO: b/390196782 - maybe move this to NotificationProgressBar, because that's the only
          place this is used actually. Same for NotificationProgressDrawable.segSegGap/segPointGap
          above. -->
         <!-- Minimum required drawing width. The drawing width refers to the width after
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 3f65754..7baaa6d 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -116,7 +116,7 @@
     <public name="adServiceTypes" />
     <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") -->
     <public name="languageSettingsActivity"/>
-    <!-- @FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM") -->
+    <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) -->
     <public name="dreamCategory"/>
     <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled")
          @hide @SystemApi -->
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
index 44b2d90..dfe7d03 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -26,7 +26,6 @@
 import android.content.pm.ServiceInfo;
 import android.os.Bundle;
 import android.os.Parcel;
-import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
 
@@ -124,7 +123,6 @@
     }
 
     @Test
-    @EnableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME)
     public void testIsVirtualDeviceOnly() throws Exception {
         final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_virtual_device_only);
 
diff --git a/core/tests/coretests/src/android/app/NotificationManagerTest.java b/core/tests/coretests/src/android/app/NotificationManagerTest.java
index 18ba6a1..4143229 100644
--- a/core/tests/coretests/src/android/app/NotificationManagerTest.java
+++ b/core/tests/coretests/src/android/app/NotificationManagerTest.java
@@ -30,8 +30,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.os.UserHandle;
 import android.platform.test.annotations.EnableFlags;
@@ -263,6 +265,38 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY,
+            Flags.FLAG_NM_BINDER_PERF_LOG_NM_THROTTLING})
+    public void notify_rapidUpdate_logsOncePerSecond() throws Exception {
+        Notification n = exampleNotification();
+
+        for (int i = 0; i < 650; i++) {
+            mNotificationManager.notify(1, n);
+            mClock.advanceByMillis(10);
+        }
+
+        // Runs for a total of 6.5 seconds, so should log once (when RateEstimator catches up) + 6
+        // more times (after 1 second each).
+        verify(mNotificationManager.mBackendService, times(7)).incrementCounter(
+                eq("notifications.value_client_throttled_notify_update"));
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY,
+            Flags.FLAG_NM_BINDER_PERF_LOG_NM_THROTTLING})
+    public void cancel_unnecessaryAndRapid_logsOncePerSecond() throws Exception {
+        for (int i = 0; i < 650; i++) {
+            mNotificationManager.cancel(1);
+            mClock.advanceByMillis(10);
+        }
+
+        // Runs for a total of 6.5 seconds, so should log once (when RateEstimator catches up) + 6
+        // more times (after 1 second each).
+        verify(mNotificationManager.mBackendService, times(7)).incrementCounter(
+                eq("notifications.value_client_throttled_cancel_duplicate"));
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
     public void getNotificationChannel_cachedUntilInvalidated() throws Exception {
         // Invalidate the cache first because the cache won't do anything until then
@@ -409,6 +443,46 @@
                 .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
     }
 
+    @Test
+    @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+    public void areAutomaticZenRulesUserManaged_handheld_isTrue() {
+        PackageManager pm = mock(PackageManager.class);
+        when(pm.hasSystemFeature(any())).thenReturn(false);
+        mContext.setPackageManager(pm);
+
+        assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isTrue();
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+    public void areAutomaticZenRulesUserManaged_auto_isFalse() {
+        PackageManager pm = mock(PackageManager.class);
+        when(pm.hasSystemFeature(eq(PackageManager.FEATURE_AUTOMOTIVE))).thenReturn(true);
+        mContext.setPackageManager(pm);
+
+        assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isFalse();
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+    public void areAutomaticZenRulesUserManaged_tv_isFalse() {
+        PackageManager pm = mock(PackageManager.class);
+        when(pm.hasSystemFeature(eq(PackageManager.FEATURE_LEANBACK))).thenReturn(true);
+        mContext.setPackageManager(pm);
+
+        assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isFalse();
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+    public void areAutomaticZenRulesUserManaged_watch_isFalse() {
+        PackageManager pm = mock(PackageManager.class);
+        when(pm.hasSystemFeature(eq(PackageManager.FEATURE_WATCH))).thenReturn(true);
+        mContext.setPackageManager(pm);
+
+        assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isFalse();
+    }
+
     private Notification exampleNotification() {
         return new Notification.Builder(mContext, "channel")
                 .setSmallIcon(android.R.drawable.star_big_on)
@@ -438,6 +512,7 @@
     // Helper context wrapper class where we can control just the return values of getPackageName,
     // getOpPackageName, and getUserId (used in getNotificationChannels).
     private static class PackageTestableContext extends ContextWrapper {
+        private PackageManager mPm;
         private String mPackage;
         private String mOpPackage;
         private Integer mUserId;
@@ -446,6 +521,10 @@
             super(base);
         }
 
+        void setPackageManager(@Nullable PackageManager pm) {
+            mPm = pm;
+        }
+
         void setParameters(String packageName, String opPackageName, int userId) {
             mPackage = packageName;
             mOpPackage = opPackageName;
@@ -453,6 +532,12 @@
         }
 
         @Override
+        public PackageManager getPackageManager() {
+            if (mPm != null) return mPm;
+            return super.getPackageManager();
+        }
+
+        @Override
         public String getPackageName() {
             if (mPackage != null) return mPackage;
             return super.getPackageName();
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 939bf2e..ee4761b 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -76,6 +76,10 @@
         mUsers = new ArrayList<>();
         mUsers.add(new UserInfo(0, "Owner", UserInfo.FLAG_ADMIN));
         mUsers.add(new UserInfo(1, "User1", 0));
+        addServiceInfoIntoResolveInfo(r1, "r1.package.name" /* packageName */,
+                "r1.service.name" /* serviceName */);
+        addServiceInfoIntoResolveInfo(r2, "r2.package.name" /* packageName */,
+                "r2.service.name" /* serviceName */);
     }
 
     public void testGetAllServicesHappyPath() {
@@ -218,6 +222,14 @@
         assertTrue("File should be created at " + file, file.length() > 0);
     }
 
+    private void addServiceInfoIntoResolveInfo(ResolveInfo resolveInfo, String packageName,
+            String serviceName) {
+        final ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.packageName = packageName;
+        serviceInfo.name = serviceName;
+        resolveInfo.serviceInfo = serviceInfo;
+    }
+
     /**
      * Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing
      */
@@ -301,8 +313,8 @@
         }
 
         @Override
-        protected ServiceInfo<TestServiceType> parseServiceInfo(
-                ResolveInfo resolveInfo, int userId) throws XmlPullParserException, IOException {
+        protected ServiceInfo<TestServiceType> parseServiceInfo(ResolveInfo resolveInfo,
+                long lastUpdateTime) throws XmlPullParserException, IOException {
             int size = mServices.size();
             for (int i = 0; i < size; i++) {
                 Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);
diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
index 292f750..ad28383 100644
--- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java
+++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
@@ -112,15 +112,14 @@
 
         long ptr = nativeStartTracing(traceConfig.toByteArray());
 
-        PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder()
+        PerfettoTrace.instant(FOO_CATEGORY, "event")
                 .addFlow(2)
                 .addTerminatingFlow(3)
                 .addArg("long_val", 10000000000L)
                 .addArg("bool_val", true)
                 .addArg("double_val", 3.14)
                 .addArg("string_val", FOO)
-                .build();
-        PerfettoTrace.instant(FOO_CATEGORY, "event", extra);
+                .emit();
 
         byte[] traceBytes = nativeStopTracing(ptr);
 
@@ -163,12 +162,12 @@
 
     @Test
     @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
-    public void testDebugAnnotationsWithLamda() throws Exception {
+    public void testDebugAnnotationsWithLambda() throws Exception {
         TraceConfig traceConfig = getTraceConfig(FOO);
 
         long ptr = nativeStartTracing(traceConfig.toByteArray());
 
-        PerfettoTrace.instant(FOO_CATEGORY, "event", e -> e.addArg("long_val", 123L));
+        PerfettoTrace.instant(FOO_CATEGORY, "event").addArg("long_val", 123L).emit();
 
         byte[] traceBytes = nativeStopTracing(ptr);
 
@@ -203,15 +202,14 @@
 
         long ptr = nativeStartTracing(traceConfig.toByteArray());
 
-        PerfettoTrackEventExtra beginExtra = PerfettoTrackEventExtra.builder()
-                .usingNamedTrack(FOO, PerfettoTrace.getProcessTrackUuid())
-                .build();
-        PerfettoTrace.begin(FOO_CATEGORY, "event", beginExtra);
+        PerfettoTrace.begin(FOO_CATEGORY, "event")
+                .usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), FOO)
+                .emit();
 
-        PerfettoTrackEventExtra endExtra = PerfettoTrackEventExtra.builder()
-                .usingNamedTrack("bar", PerfettoTrace.getThreadTrackUuid(Process.myTid()))
-                .build();
-        PerfettoTrace.end(FOO_CATEGORY, endExtra);
+
+        PerfettoTrace.end(FOO_CATEGORY)
+                .usingNamedTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()), "bar")
+                .emit();
 
         Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
 
@@ -242,26 +240,67 @@
         assertThat(hasTrackUuid).isTrue();
         assertThat(mCategoryNames).contains(FOO);
         assertThat(mTrackNames).contains(FOO);
+        assertThat(mTrackNames).contains("bar");
     }
 
     @Test
     @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
-    public void testCounter() throws Exception {
+    public void testProcessThreadNamedTrack() throws Exception {
         TraceConfig traceConfig = getTraceConfig(FOO);
 
         long ptr = nativeStartTracing(traceConfig.toByteArray());
 
-        PerfettoTrackEventExtra intExtra = PerfettoTrackEventExtra.builder()
-                .usingCounterTrack(FOO, PerfettoTrace.getProcessTrackUuid())
-                .setCounter(16)
-                .build();
-        PerfettoTrace.counter(FOO_CATEGORY, intExtra);
+        PerfettoTrace.begin(FOO_CATEGORY, "event")
+                .usingProcessNamedTrack(FOO)
+                .emit();
 
-        PerfettoTrackEventExtra doubleExtra = PerfettoTrackEventExtra.builder()
-                .usingCounterTrack("bar", PerfettoTrace.getProcessTrackUuid())
-                .setCounter(3.14)
-                .build();
-        PerfettoTrace.counter(FOO_CATEGORY, doubleExtra);
+
+        PerfettoTrace.end(FOO_CATEGORY)
+                .usingThreadNamedTrack(Process.myTid(), "%s-%s", "bar", "stool")
+                .emit();
+
+        Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+
+        boolean hasTrackEvent = false;
+        boolean hasTrackUuid = false;
+        for (TracePacket packet: trace.getPacketList()) {
+            TrackEvent event;
+            if (packet.hasTrackEvent()) {
+                hasTrackEvent = true;
+                event = packet.getTrackEvent();
+
+                if (TrackEvent.Type.TYPE_SLICE_BEGIN.equals(event.getType())
+                        && event.hasTrackUuid()) {
+                    hasTrackUuid = true;
+                }
+
+                if (TrackEvent.Type.TYPE_SLICE_END.equals(event.getType())
+                        && event.hasTrackUuid()) {
+                    hasTrackUuid &= true;
+                }
+            }
+
+            collectInternedData(packet);
+            collectTrackNames(packet);
+        }
+
+        assertThat(hasTrackEvent).isTrue();
+        assertThat(hasTrackUuid).isTrue();
+        assertThat(mCategoryNames).contains(FOO);
+        assertThat(mTrackNames).contains(FOO);
+        assertThat(mTrackNames).contains("bar-stool");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+    public void testCounterSimple() throws Exception {
+        TraceConfig traceConfig = getTraceConfig(FOO);
+
+        long ptr = nativeStartTracing(traceConfig.toByteArray());
+
+        PerfettoTrace.counter(FOO_CATEGORY, 16, FOO).emit();
+
+        PerfettoTrace.counter(FOO_CATEGORY, 3.14, "bar").emit();
 
         Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
 
@@ -297,12 +336,102 @@
 
     @Test
     @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+    public void testCounter() throws Exception {
+        TraceConfig traceConfig = getTraceConfig(FOO);
+
+        long ptr = nativeStartTracing(traceConfig.toByteArray());
+
+        PerfettoTrace.counter(FOO_CATEGORY, 16)
+                .usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), FOO).emit();
+
+        PerfettoTrace.counter(FOO_CATEGORY, 3.14)
+                .usingCounterTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()),
+                                   "%s-%s", "bar", "stool").emit();
+
+        Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+
+        boolean hasTrackEvent = false;
+        boolean hasCounterValue = false;
+        boolean hasDoubleCounterValue = false;
+        for (TracePacket packet: trace.getPacketList()) {
+            TrackEvent event;
+            if (packet.hasTrackEvent()) {
+                hasTrackEvent = true;
+                event = packet.getTrackEvent();
+
+                if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType())
+                        && event.getCounterValue() == 16) {
+                    hasCounterValue = true;
+                }
+
+                if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType())
+                        && event.getDoubleCounterValue() == 3.14) {
+                    hasDoubleCounterValue = true;
+                }
+            }
+
+            collectTrackNames(packet);
+        }
+
+        assertThat(hasTrackEvent).isTrue();
+        assertThat(hasCounterValue).isTrue();
+        assertThat(hasDoubleCounterValue).isTrue();
+        assertThat(mTrackNames).contains(FOO);
+        assertThat(mTrackNames).contains("bar-stool");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+    public void testProcessThreadCounter() throws Exception {
+        TraceConfig traceConfig = getTraceConfig(FOO);
+
+        long ptr = nativeStartTracing(traceConfig.toByteArray());
+
+        PerfettoTrace.counter(FOO_CATEGORY, 16).usingProcessCounterTrack(FOO).emit();
+
+        PerfettoTrace.counter(FOO_CATEGORY, 3.14)
+                .usingThreadCounterTrack(Process.myTid(), "%s-%s", "bar", "stool").emit();
+
+        Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+
+        boolean hasTrackEvent = false;
+        boolean hasCounterValue = false;
+        boolean hasDoubleCounterValue = false;
+        for (TracePacket packet: trace.getPacketList()) {
+            TrackEvent event;
+            if (packet.hasTrackEvent()) {
+                hasTrackEvent = true;
+                event = packet.getTrackEvent();
+
+                if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType())
+                        && event.getCounterValue() == 16) {
+                    hasCounterValue = true;
+                }
+
+                if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType())
+                        && event.getDoubleCounterValue() == 3.14) {
+                    hasDoubleCounterValue = true;
+                }
+            }
+
+            collectTrackNames(packet);
+        }
+
+        assertThat(hasTrackEvent).isTrue();
+        assertThat(hasCounterValue).isTrue();
+        assertThat(hasDoubleCounterValue).isTrue();
+        assertThat(mTrackNames).contains(FOO);
+        assertThat(mTrackNames).contains("bar-stool");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
     public void testProto() throws Exception {
         TraceConfig traceConfig = getTraceConfig(FOO);
 
         long ptr = nativeStartTracing(traceConfig.toByteArray());
 
-        PerfettoTrackEventExtra extra5 = PerfettoTrackEventExtra.builder()
+        PerfettoTrace.instant(FOO_CATEGORY, "event_proto")
                 .beginProto()
                 .beginNested(33L)
                 .addField(4L, 2L)
@@ -310,8 +439,7 @@
                 .endNested()
                 .addField(2001, "AIDL::IActivityManager")
                 .endProto()
-                .build();
-        PerfettoTrace.instant(FOO_CATEGORY, "event_proto", extra5);
+                .emit();
 
         byte[] traceBytes = nativeStopTracing(ptr);
 
@@ -351,7 +479,7 @@
 
         long ptr = nativeStartTracing(traceConfig.toByteArray());
 
-        PerfettoTrackEventExtra extra6 = PerfettoTrackEventExtra.builder()
+        PerfettoTrace.instant(FOO_CATEGORY, "event_proto_nested")
                 .beginProto()
                 .beginNested(29L)
                 .beginNested(4L)
@@ -364,8 +492,7 @@
                 .endNested()
                 .endNested()
                 .endProto()
-                .build();
-        PerfettoTrace.instant(FOO_CATEGORY, "event_proto_nested", extra6);
+                .emit();
 
         byte[] traceBytes = nativeStopTracing(ptr);
 
@@ -413,8 +540,7 @@
 
         long ptr = nativeStartTracing(traceConfig.toByteArray());
 
-        PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder().build();
-        PerfettoTrace.instant(FOO_CATEGORY, "event_trigger", extra);
+        PerfettoTrace.instant(FOO_CATEGORY, "event_trigger").emit();
 
         PerfettoTrace.activateTrigger(FOO, 1000);
 
@@ -439,49 +565,21 @@
 
     @Test
     @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
-    public void testMultipleExtras() throws Exception {
-        boolean hasException = false;
-        try {
-            PerfettoTrackEventExtra.builder();
-
-            // Unclosed extra will throw an exception here
-            PerfettoTrackEventExtra.builder();
-        } catch (Exception e) {
-            hasException = true;
-        }
-
-        try {
-            PerfettoTrackEventExtra.builder().build();
-
-            // Closed extra but unused (reset hasn't been called internally) will throw an exception
-            // here.
-            PerfettoTrackEventExtra.builder();
-        } catch (Exception e) {
-            hasException &= true;
-        }
-
-        assertThat(hasException).isTrue();
-    }
-
-    @Test
-    @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
     public void testRegister() throws Exception {
         TraceConfig traceConfig = getTraceConfig(BAR);
 
         Category barCategory = new Category(BAR);
         long ptr = nativeStartTracing(traceConfig.toByteArray());
 
-        PerfettoTrackEventExtra beforeExtra = PerfettoTrackEventExtra.builder()
+        PerfettoTrace.instant(barCategory, "event")
                 .addArg("before", 1)
-                .build();
-        PerfettoTrace.instant(barCategory, "event", beforeExtra);
+                .emit();
 
         barCategory.register();
 
-        PerfettoTrackEventExtra afterExtra = PerfettoTrackEventExtra.builder()
+        PerfettoTrace.instant(barCategory, "event")
                 .addArg("after", 1)
-                .build();
-        PerfettoTrace.instant(barCategory, "event", afterExtra);
+                .emit();
 
         byte[] traceBytes = nativeStopTracing(ptr);
 
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index f87b699..ee8d428 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -224,7 +224,7 @@
         }
 
         @Override
-        void flush(int reason) {
+        void internalFlush(int reason) {
             throw new UnsupportedOperationException("should not have been called");
         }
 
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index 4a5123e..a1d7f87 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -263,7 +263,7 @@
         session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
         session.mDirectServiceInterface = mMockContentCaptureDirectManager;
 
-        session.flush(REASON);
+        session.internalFlush(REASON);
         mTestableLooper.processAllMessages();
 
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -280,7 +280,7 @@
         session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
         session.mDirectServiceInterface = mMockContentCaptureDirectManager;
 
-        session.flush(REASON);
+        session.internalFlush(REASON);
         mTestableLooper.processAllMessages();
 
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -298,7 +298,7 @@
         session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
         session.mDirectServiceInterface = mMockContentCaptureDirectManager;
 
-        session.flush(REASON);
+        session.internalFlush(REASON);
         mTestableLooper.processAllMessages();
 
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -316,7 +316,7 @@
         session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
         session.mDirectServiceInterface = mMockContentCaptureDirectManager;
 
-        session.flush(REASON);
+        session.internalFlush(REASON);
         mTestableLooper.processAllMessages();
 
         verifyZeroInteractions(mMockContentProtectionEventProcessor);
@@ -544,7 +544,7 @@
         session.mContentCaptureHandler = null;
         session.mDirectServiceInterface = null;
 
-        session.flush(REASON);
+        session.internalFlush(REASON);
 
         assertThat(session.mEvents).hasSize(1);
         assertThat(session.mEventProcessQueue).isEmpty();
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
index 9818e19..ec19c0c 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.widget;
 
+import static com.android.internal.widget.NotificationProgressBar.NotEnoughWidthToFitAllPartsException;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.Notification.ProgressStyle;
@@ -35,6 +37,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
@@ -47,7 +50,7 @@
         int progress = 50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
@@ -60,7 +63,7 @@
         int progress = 50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
@@ -73,7 +76,7 @@
         int progress = 50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
@@ -86,7 +89,7 @@
         int progress = 50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
@@ -98,20 +101,21 @@
         int progress = -50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
     @Test
-    public void processAndConvertToParts_progressIsZero() {
+    public void processAndConvertToParts_progressIsZero()
+            throws NotificationProgressBar.NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 0;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
 
@@ -123,8 +127,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 300, Color.RED)));
@@ -148,15 +153,16 @@
     }
 
     @Test
-    public void processAndConvertToParts_progressAtMax() {
+    public void processAndConvertToParts_progressAtMax()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 100;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
 
@@ -168,8 +174,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 300, Color.RED)));
@@ -195,7 +202,7 @@
         int progress = 150;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
@@ -208,7 +215,7 @@
         int progress = 50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
@@ -221,12 +228,62 @@
         int progress = 50;
         int progressMax = 100;
 
-        NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+        NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
                 progressMax);
     }
 
     @Test
-    public void processAndConvertToParts_multipleSegmentsWithoutPoints() {
+    public void processAndConvertToParts_singleSegmentWithoutPoints()
+            throws NotEnoughWidthToFitAllPartsException {
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+        List<ProgressStyle.Point> points = new ArrayList<>();
+        int progress = 60;
+        int progressMax = 100;
+
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
+
+        List<Part> expectedParts = new ArrayList<>(
+                List.of(new Segment(1, Color.BLUE)));
+
+        assertThat(parts).isEqualTo(expectedParts);
+
+        float drawableWidth = 300;
+        float segSegGap = 4;
+        float segPointGap = 4;
+        float pointRadius = 6;
+        boolean hasTrackerIcon = true;
+
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawableSegment(0, 300, Color.BLUE)));
+
+        assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+        float segmentMinWidth = 16;
+        boolean isStyledByProgress = true;
+
+        Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+                parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+        // Colors with 40% opacity
+        int fadedBlue = 0x660000FF;
+        expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawableSegment(0, 180, Color.BLUE),
+                        new DrawableSegment(180, 300, fadedBlue, true)));
+
+        assertThat(p.second).isEqualTo(180);
+        assertThat(p.first).isEqualTo(expectedDrawableParts);
+    }
+
+    @Test
+    public void processAndConvertToParts_multipleSegmentsWithoutPoints()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -234,8 +291,8 @@
         int progress = 60;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN)));
@@ -248,8 +305,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 146, Color.RED),
@@ -274,15 +332,16 @@
     }
 
     @Test
-    public void processAndConvertToParts_multipleSegmentsWithoutPoints_noTracker() {
+    public void processAndConvertToParts_multipleSegmentsWithoutPoints_noTracker()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
         List<ProgressStyle.Point> points = new ArrayList<>();
         int progress = 60;
         int progressMax = 100;
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN)));
@@ -295,8 +354,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = false;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 146, Color.RED),
@@ -321,7 +381,8 @@
     }
 
     @Test
-    public void processAndConvertToParts_singleSegmentWithPoints() {
+    public void processAndConvertToParts_singleSegmentWithPoints()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
         List<ProgressStyle.Point> points = new ArrayList<>();
@@ -332,8 +393,8 @@
         int progress = 60;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Segment(0.15f, Color.BLUE),
@@ -354,8 +415,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 35, Color.BLUE),
@@ -396,7 +458,8 @@
     }
 
     @Test
-    public void processAndConvertToParts_multipleSegmentsWithPoints() {
+    public void processAndConvertToParts_multipleSegmentsWithPoints()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -408,8 +471,8 @@
         int progress = 60;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Segment(0.15f, Color.RED),
@@ -430,8 +493,9 @@
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
@@ -473,7 +537,8 @@
     }
 
     @Test
-    public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd() {
+    public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -485,8 +550,8 @@
         int progress = 60;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Point(Color.RED),
@@ -505,8 +570,10 @@
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawablePoint(0, 12, Color.RED),
@@ -547,7 +614,8 @@
     // The points are so close to start/end that they would go out of bounds without the minimum
     // segment width requirement.
     @Test
-    public void processAndConvertToParts_multipleSegmentsWithPointsNearStartAndEnd() {
+    public void processAndConvertToParts_multipleSegmentsWithPointsNearStartAndEnd()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -559,8 +627,8 @@
         int progress = 60;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Segment(0.01f, Color.RED),
@@ -581,8 +649,10 @@
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, -7, Color.RED),
@@ -625,7 +695,8 @@
     }
 
     @Test
-    public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress() {
+    public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
         segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -636,8 +707,8 @@
         int progress = 60;
         int progressMax = 100;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Segment(0.15f, Color.RED), new Point(Color.RED),
@@ -653,8 +724,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
@@ -691,7 +763,8 @@
     // The only difference from the `zeroWidthDrawableSegment` test below is the longer
     // segmentMinWidth (= 16dp).
     @Test
-    public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment() {
+    public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
@@ -702,8 +775,8 @@
         int progress = 1000;
         int progressMax = 1000;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
@@ -717,8 +790,10 @@
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -749,7 +824,8 @@
     // The only difference from the `negativeWidthDrawableSegment` test above is the shorter
     // segmentMinWidth (= 10dp).
     @Test
-    public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment() {
+    public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
@@ -760,8 +836,8 @@
         int progress = 1000;
         int progressMax = 1000;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
@@ -775,8 +851,10 @@
         float segPointGap = 4;
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -805,7 +883,8 @@
     }
 
     @Test
-    public void maybeStretchAndRescaleSegments_noStretchingNecessary() {
+    public void maybeStretchAndRescaleSegments_noStretchingNecessary()
+            throws NotEnoughWidthToFitAllPartsException {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
         segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
@@ -816,8 +895,8 @@
         int progress = 1000;
         int progressMax = 1000;
 
-        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
-                progress, progressMax);
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
                 List.of(new Point(Color.BLUE), new Segment(0.2f, Color.BLUE),
@@ -832,8 +911,9 @@
         float pointRadius = 6;
         boolean hasTrackerIcon = true;
 
-        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
-                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
 
         List<DrawablePart> expectedDrawableParts = new ArrayList<>(
                 List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -854,4 +934,168 @@
         assertThat(p.second).isEqualTo(200);
         assertThat(p.first).isEqualTo(expectedDrawableParts);
     }
+
+    @Test(expected = NotEnoughWidthToFitAllPartsException.class)
+    public void maybeStretchAndRescaleSegments_notEnoughWidthToFitAllParts()
+            throws NotEnoughWidthToFitAllPartsException {
+        final int orange = 0xff7f50;
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(10).setColor(orange));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.YELLOW));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.BLUE));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
+        segments.add(new ProgressStyle.Segment(10).setColor(orange));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.YELLOW));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.BLUE));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
+        segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
+        List<ProgressStyle.Point> points = new ArrayList<>();
+        points.add(new ProgressStyle.Point(0).setColor(orange));
+        points.add(new ProgressStyle.Point(1).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(55).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(100).setColor(orange));
+        int progress = 50;
+        int progressMax = 100;
+
+        List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+                points, progress, progressMax);
+
+        List<Part> expectedParts = new ArrayList<>(
+                List.of(new Point(orange),
+                        new Segment(0.01f, orange),
+                        new Point(Color.BLUE),
+                        new Segment(0.09f, orange),
+                        new Segment(0.1f, Color.YELLOW),
+                        new Segment(0.1f, Color.BLUE),
+                        new Segment(0.1f, Color.GREEN),
+                        new Segment(0.1f, Color.RED),
+                        new Segment(0.05f, orange),
+                        new Point(Color.BLUE),
+                        new Segment(0.05f, orange),
+                        new Segment(0.1f, Color.YELLOW),
+                        new Segment(0.1f, Color.BLUE),
+                        new Segment(0.1f, Color.GREEN),
+                        new Segment(0.1f, Color.RED),
+                        new Point(orange)));
+
+        assertThat(parts).isEqualTo(expectedParts);
+
+        // For the list of ProgressStyle.Part used in this test, 300 is the minimum width.
+        float drawableWidth = 299;
+        float segSegGap = 4;
+        float segPointGap = 4;
+        float pointRadius = 6;
+        boolean hasTrackerIcon = true;
+
+        List<DrawablePart> drawableParts =
+                NotificationProgressBar.processPartsAndConvertToDrawableParts(
+                        parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        // Skips the validation of the intermediate list of DrawableParts.
+
+        float segmentMinWidth = 16;
+        boolean isStyledByProgress = true;
+
+        NotificationProgressBar.maybeStretchAndRescaleSegments(
+                parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+    }
+
+    @Test
+    public void processModelAndConvertToFinalDrawableParts_singleSegmentWithPoints()
+            throws NotEnoughWidthToFitAllPartsException {
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+        List<ProgressStyle.Point> points = new ArrayList<>();
+        points.add(new ProgressStyle.Point(15).setColor(Color.RED));
+        points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
+        int progress = 60;
+        int progressMax = 100;
+
+        float drawableWidth = 300;
+        float segSegGap = 4;
+        float segPointGap = 4;
+        float pointRadius = 6;
+        boolean hasTrackerIcon = true;
+
+        float segmentMinWidth = 16;
+        boolean isStyledByProgress = true;
+
+        Pair<List<DrawablePart>, Float> p =
+                NotificationProgressBar.processModelAndConvertToFinalDrawableParts(
+                        segments,
+                        points,
+                        progress,
+                        progressMax,
+                        drawableWidth,
+                        segSegGap,
+                        segPointGap,
+                        pointRadius,
+                        hasTrackerIcon,
+                        segmentMinWidth,
+                        isStyledByProgress
+                );
+
+        // Colors with 40% opacity
+        int fadedBlue = 0x660000FF;
+        int fadedYellow = 0x66FFFF00;
+        List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawableSegment(0, 34.219177F, Color.BLUE),
+                        new DrawablePoint(38.219177F, 50.219177F, Color.RED),
+                        new DrawableSegment(54.219177F, 70.21918F, Color.BLUE),
+                        new DrawablePoint(74.21918F, 86.21918F, Color.BLUE),
+                        new DrawableSegment(90.21918F, 172.38356F, Color.BLUE),
+                        new DrawablePoint(176.38356F, 188.38356F, Color.BLUE),
+                        new DrawableSegment(192.38356F, 217.0137F, fadedBlue, true),
+                        new DrawablePoint(221.0137F, 233.0137F, fadedYellow),
+                        new DrawableSegment(237.0137F, 300F, fadedBlue, true)));
+
+        assertThat(p.second).isEqualTo(182.38356F);
+        assertThat(p.first).isEqualTo(expectedDrawableParts);
+    }
+
+    @Test
+    public void processModelAndConvertToFinalDrawableParts_singleSegmentWithoutPoints()
+            throws NotEnoughWidthToFitAllPartsException {
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+        int progress = 60;
+        int progressMax = 100;
+
+        float drawableWidth = 100;
+        float segSegGap = 4;
+        float segPointGap = 4;
+        float pointRadius = 6;
+        boolean hasTrackerIcon = true;
+
+        float segmentMinWidth = 16;
+        boolean isStyledByProgress = true;
+
+        Pair<List<DrawablePart>, Float> p =
+                NotificationProgressBar.processModelAndConvertToFinalDrawableParts(
+                        segments,
+                        Collections.emptyList(),
+                        progress,
+                        progressMax,
+                        drawableWidth,
+                        segSegGap,
+                        segPointGap,
+                        pointRadius,
+                        hasTrackerIcon,
+                        segmentMinWidth,
+                        isStyledByProgress
+                );
+
+        // Colors with 40% opacity
+        int fadedBlue = 0x660000FF;
+        List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawableSegment(0, 60.000004F, Color.BLUE),
+                        new DrawableSegment(60.000004F, 100, fadedBlue, true)));
+
+        assertThat(p.second).isWithin(1e-5f).of(60);
+        assertThat(p.first).isEqualTo(expectedDrawableParts);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
index 962399e..e1f5b1c 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
@@ -49,7 +49,8 @@
         new NotificationProgressModel(List.of(),
                 List.of(),
                 10,
-                false);
+                false,
+                Color.TRANSPARENT);
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -58,7 +59,8 @@
                 List.of(new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW)),
                 List.of(),
                 -1,
-                false);
+                false,
+                Color.TRANSPARENT);
     }
 
     @Test
@@ -74,14 +76,15 @@
         // THEN
         assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.RED);
         assertThat(restoredModel.isIndeterminate()).isTrue();
-        assertThat(restoredModel.getProgress()).isEqualTo(-1);
+        assertThat(restoredModel.getProgress()).isEqualTo(0);
         assertThat(restoredModel.getSegments()).isEmpty();
         assertThat(restoredModel.getPoints()).isEmpty();
         assertThat(restoredModel.isStyledByProgress()).isFalse();
+        assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.TRANSPARENT);
     }
 
     @Test
-    public void save_and_restore_non_indeterminate_progress_model() {
+    public void save_and_restore_determinate_progress_model() {
         // GIVEN
         final List<Notification.ProgressStyle.Segment> segments = List.of(
                 new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
@@ -92,7 +95,8 @@
         final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
                 points,
                 100,
-                true);
+                true,
+                Color.RED);
 
         final Bundle bundle = savedModel.toBundle();
 
@@ -106,6 +110,38 @@
         assertThat(restoredModel.getPoints()).isEqualTo(points);
         assertThat(restoredModel.getProgress()).isEqualTo(100);
         assertThat(restoredModel.isStyledByProgress()).isTrue();
-        assertThat(restoredModel.getIndeterminateColor()).isEqualTo(-1);
+        assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.RED);
+        assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.TRANSPARENT);
+    }
+
+    @Test
+    public void save_and_restore_non_determinate_progress_model_segments_fallback_color_invalid() {
+        // GIVEN
+        final List<Notification.ProgressStyle.Segment> segments = List.of(
+                new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
+                new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW));
+        final List<Notification.ProgressStyle.Point> points = List.of(
+                new Notification.ProgressStyle.Point(0).setColor(Color.RED),
+                new Notification.ProgressStyle.Point(20).setColor(Color.BLUE));
+        final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
+                points,
+                100,
+                true,
+                Color.TRANSPARENT);
+
+        final Bundle bundle = savedModel.toBundle();
+
+        // WHEN
+        final NotificationProgressModel restoredModel =
+                NotificationProgressModel.fromBundle(bundle);
+
+        // THEN
+        assertThat(restoredModel.isIndeterminate()).isFalse();
+        assertThat(restoredModel.getSegments()).isEqualTo(segments);
+        assertThat(restoredModel.getPoints()).isEqualTo(points);
+        assertThat(restoredModel.getProgress()).isEqualTo(100);
+        assertThat(restoredModel.isStyledByProgress()).isTrue();
+        assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.TRANSPARENT);
+        assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.TRANSPARENT);
     }
 }
diff --git a/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java b/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java
new file mode 100644
index 0000000..76d0aaa
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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 android.util.proto;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+
+/**
+ * Unit tests for {@link android.util.proto.ProtoFieldFilter}.
+ *
+ *  Build/Install/Run:
+ *  atest FrameworksCoreTests:ProtoFieldFilterTest
+ *
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ProtoFieldFilterTest {
+
+    private static final class FieldTypes {
+        static final long INT64 = ProtoStream.FIELD_TYPE_INT64 | ProtoStream.FIELD_COUNT_SINGLE;
+        static final long FIXED64 = ProtoStream.FIELD_TYPE_FIXED64 | ProtoStream.FIELD_COUNT_SINGLE;
+        static final long BYTES = ProtoStream.FIELD_TYPE_BYTES | ProtoStream.FIELD_COUNT_SINGLE;
+        static final long FIXED32 = ProtoStream.FIELD_TYPE_FIXED32 | ProtoStream.FIELD_COUNT_SINGLE;
+        static final long MESSAGE = ProtoStream.FIELD_TYPE_MESSAGE | ProtoStream.FIELD_COUNT_SINGLE;
+        static final long INT32 = ProtoStream.FIELD_TYPE_INT32 | ProtoStream.FIELD_COUNT_SINGLE;
+    }
+
+    private static ProtoOutputStream createBasicTestProto() {
+        ProtoOutputStream out = new ProtoOutputStream();
+
+        out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+        out.writeFixed64(ProtoStream.makeFieldId(2, FieldTypes.FIXED64), 0x1234567890ABCDEFL);
+        out.writeBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES), new byte[]{1, 2, 3, 4, 5});
+        out.writeFixed32(ProtoStream.makeFieldId(4, FieldTypes.FIXED32), 0xDEADBEEF);
+
+        return out;
+    }
+
+    private static byte[] filterProto(byte[] input, ProtoFieldFilter filter) throws IOException {
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(input);
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        filter.filter(inputStream, outputStream);
+        return outputStream.toByteArray();
+    }
+
+    @Test
+    public void testNoFieldsFiltered() throws IOException {
+        byte[] input = createBasicTestProto().getBytes();
+        byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> true));
+        assertArrayEquals("No fields should be filtered out", input, output);
+    }
+
+    @Test
+    public void testAllFieldsFiltered() throws IOException {
+        byte[] input = createBasicTestProto().getBytes();
+        byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> false));
+
+        assertEquals("All fields should be filtered out", 0, output.length);
+    }
+
+    @Test
+    public void testSpecificFieldsFiltered() throws IOException {
+
+        ProtoOutputStream out = createBasicTestProto();
+        byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(n -> n != 2));
+
+        ProtoInputStream in = new ProtoInputStream(output);
+        boolean[] fieldsFound = new boolean[5];
+
+        int fieldNumber;
+        while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+            fieldsFound[fieldNumber] = true;
+            switch (fieldNumber) {
+                case 1:
+                    assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+                    break;
+                case 2:
+                    fail("Field 2 should be filtered out");
+                    break;
+                case 3:
+                    assertArrayEquals(new byte[]{1, 2, 3, 4, 5},
+                            in.readBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES)));
+                    break;
+                case 4:
+                    assertEquals(0xDEADBEEF,
+                            in.readInt(ProtoStream.makeFieldId(4, FieldTypes.FIXED32)));
+                    break;
+                default:
+                    fail("Unexpected field number: " + fieldNumber);
+            }
+        }
+
+        assertTrue("Field 1 should be present", fieldsFound[1]);
+        assertFalse("Field 2 should be filtered", fieldsFound[2]);
+        assertTrue("Field 3 should be present", fieldsFound[3]);
+        assertTrue("Field 4 should be present", fieldsFound[4]);
+    }
+
+    @Test
+    public void testDifferentWireTypes() throws IOException {
+        ProtoOutputStream out = new ProtoOutputStream();
+
+        out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+        out.writeFixed64(ProtoStream.makeFieldId(2, FieldTypes.FIXED64), 0x1234567890ABCDEFL);
+        out.writeBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES), new byte[]{10, 20, 30});
+
+        long token = out.start(ProtoStream.makeFieldId(4, FieldTypes.MESSAGE));
+        out.writeInt32(ProtoStream.makeFieldId(1, FieldTypes.INT32), 42);
+        out.end(token);
+
+        out.writeFixed32(ProtoStream.makeFieldId(5, FieldTypes.FIXED32), 0xDEADBEEF);
+
+        byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(fieldNumber -> true));
+
+        ProtoInputStream in = new ProtoInputStream(output);
+        boolean[] fieldsFound = new boolean[6];
+
+        int fieldNumber;
+        while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+            fieldsFound[fieldNumber] = true;
+            switch (fieldNumber) {
+                case 1:
+                    assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+                    break;
+                case 2:
+                    assertEquals(0x1234567890ABCDEFL,
+                            in.readLong(ProtoStream.makeFieldId(2, FieldTypes.FIXED64)));
+                    break;
+                case 3:
+                    assertArrayEquals(new byte[]{10, 20, 30},
+                            in.readBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES)));
+                    break;
+                case 4:
+                    token = in.start(ProtoStream.makeFieldId(4, FieldTypes.MESSAGE));
+                    assertTrue(in.nextField() == 1);
+                    assertEquals(42, in.readInt(ProtoStream.makeFieldId(1, FieldTypes.INT32)));
+                    assertTrue(in.nextField() == ProtoInputStream.NO_MORE_FIELDS);
+                    in.end(token);
+                    break;
+                case 5:
+                    assertEquals(0xDEADBEEF,
+                            in.readInt(ProtoStream.makeFieldId(5, FieldTypes.FIXED32)));
+                    break;
+                default:
+                    fail("Unexpected field number: " + fieldNumber);
+            }
+        }
+
+        assertTrue("All fields should be present",
+                fieldsFound[1] && fieldsFound[2] && fieldsFound[3]
+                && fieldsFound[4] && fieldsFound[5]);
+    }
+    @Test
+    public void testNestedMessagesUnfiltered() throws IOException {
+        ProtoOutputStream out = new ProtoOutputStream();
+
+        out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+
+        long token = out.start(ProtoStream.makeFieldId(2, FieldTypes.MESSAGE));
+        out.writeInt32(ProtoStream.makeFieldId(1, FieldTypes.INT32), 6789);
+        out.writeFixed32(ProtoStream.makeFieldId(2, FieldTypes.FIXED32), 0xCAFEBABE);
+        out.end(token);
+
+        byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(n -> n != 2));
+
+        // Verify output
+        ProtoInputStream in = new ProtoInputStream(output);
+        boolean[] fieldsFound = new boolean[3];
+
+        int fieldNumber;
+        while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+            fieldsFound[fieldNumber] = true;
+            if (fieldNumber == 1) {
+                assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+            } else {
+                fail("Unexpected field number: " + fieldNumber);
+            }
+        }
+
+        assertTrue("Field 1 should be present", fieldsFound[1]);
+        assertFalse("Field 2 should be filtered out", fieldsFound[2]);
+    }
+
+    @Test
+    public void testRepeatedFields() throws IOException {
+
+        ProtoOutputStream out = new ProtoOutputStream();
+        long fieldId = ProtoStream.makeFieldId(1,
+                ProtoStream.FIELD_TYPE_INT32 | ProtoStream.FIELD_COUNT_REPEATED);
+
+        out.writeRepeatedInt32(fieldId, 100);
+        out.writeRepeatedInt32(fieldId, 200);
+        out.writeRepeatedInt32(fieldId, 300);
+
+        byte[] input = out.getBytes();
+
+        byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> true));
+
+        assertArrayEquals("Repeated fields should be preserved", input, output);
+    }
+
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index a30570a..b8059d0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -613,6 +613,10 @@
         <permission name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
         <!-- Permission required for CTS test - CtsContentProviderMultiUserTest -->
         <permission name="android.permission.RESOLVE_COMPONENT_FOR_UID"/>
+        <!-- Permission required for CTS test - MediaQualityTest -->
+        <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"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 1c34e0d..9b9be244 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -61,8 +61,10 @@
         /**
          * Indicates that the client is waiting on buffer release
          * due to no free buffers being available to render into.
+         * @param durationNanos The length of time in nanoseconds
+         * that the client was blocked on buffer release.
          */
-        void onWaitForBufferRelease();
+        void onWaitForBufferRelease(long durationNanos);
     }
 
     /** Create a new connection with the surface flinger. */
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 4c7e477..d0d1721 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -73,6 +73,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.OperationCanceledException;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
@@ -1106,7 +1107,12 @@
     void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
             @Nullable IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
             @TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception) {
-        Log.e(TAG, "onTaskFragmentError=" + exception.getMessage());
+        if (exception instanceof OperationCanceledException) {
+            // This is a non-fatal error and the operation just canceled.
+            Log.i(TAG, "operation canceled:" + exception.getMessage());
+        } else {
+            Log.e(TAG, "onTaskFragmentError=" + exception.getMessage(), exception);
+        }
         switch (opType) {
             case OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
             case OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index b10b099..e421026 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -134,6 +134,16 @@
 }
 
 flag {
+    name: "enable_recents_bookend_transition"
+    namespace: "multitasking"
+    description: "Use a finish-transition to clean up recents instead of the finish-WCT"
+    bug: "346588978"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "bubble_view_info_executors"
     namespace: "multitasking"
     description: "Use executors to inflate bubble views"
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
index 90ea7d3..dd387b3 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -51,6 +51,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskViewRepository
 import com.android.wm.shell.taskview.TaskViewTransitions
 import com.android.wm.shell.transition.Transitions
 import com.google.common.truth.Truth.assertThat
@@ -282,6 +283,7 @@
             mainExecutor,
             mock<Handler>(),
             bgExecutor,
+            mock<TaskViewRepository>(),
             mock<TaskViewTransitions>(),
             mock<Transitions>(),
             SyncTransactionQueue(TransactionPool(), mainExecutor),
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index a7eebd6..9d445f0 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -36,6 +36,8 @@
 import com.android.launcher3.icons.BubbleIconFactory
 import com.android.wm.shell.Flags
 import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.BubbleStackView.SurfaceSynchronizer
+import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
 import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
 import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
 import com.android.wm.shell.common.FloatingContentCoordinator
@@ -75,6 +77,7 @@
     private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
     private lateinit var bubbleData: BubbleData
     private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager
+    private lateinit var surfaceSynchronizer: FakeSurfaceSynchronizer
     private var sysuiProxy = mock<SysuiProxy>()
 
     @Before
@@ -108,13 +111,14 @@
         bubbleStackViewManager = FakeBubbleStackViewManager()
         expandedViewManager = FakeBubbleExpandedViewManager()
         bubbleTaskViewFactory = FakeBubbleTaskViewFactory(context, shellExecutor)
+        surfaceSynchronizer = FakeSurfaceSynchronizer()
         bubbleStackView =
             BubbleStackView(
                 context,
                 bubbleStackViewManager,
                 positioner,
                 bubbleData,
-                null,
+                surfaceSynchronizer,
                 FloatingContentCoordinator(),
                 { sysuiProxy },
                 shellExecutor
@@ -309,6 +313,7 @@
 
     @Test
     fun tapDifferentBubble_shouldReorder() {
+        surfaceSynchronizer.isActive = false
         val bubble1 = createAndInflateChatBubble(key = "bubble1")
         val bubble2 = createAndInflateChatBubble(key = "bubble2")
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -378,6 +383,147 @@
             .inOrder()
     }
 
+    @Test
+    fun tapDifferentBubble_imeVisible_shouldWaitForIme() {
+        val bubble1 = createAndInflateChatBubble(key = "bubble1")
+        val bubble2 = createAndInflateChatBubble(key = "bubble2")
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubbleStackView.addBubble(bubble1)
+            bubbleStackView.addBubble(bubble2)
+        }
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+        assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
+        assertThat(bubbleData.bubbles).hasSize(2)
+        assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
+        assertThat(bubble2.iconView).isNotNull()
+
+        val expandListener = FakeBubbleExpandListener()
+        bubbleStackView.setExpandListener(expandListener)
+
+        var lastUpdate: BubbleData.Update? = null
+        val semaphore = Semaphore(0)
+        val listener =
+            BubbleData.Listener { update ->
+                lastUpdate = update
+                semaphore.release()
+            }
+        bubbleData.setListener(listener)
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubble2.iconView!!.performClick()
+            assertThat(bubbleData.isExpanded).isTrue()
+
+            bubbleStackView.setSelectedBubble(bubble2)
+            bubbleStackView.isExpanded = true
+            shellExecutor.flushAll()
+        }
+
+        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+        assertThat(lastUpdate!!.expanded).isTrue()
+        assertThat(lastUpdate!!.bubbles.map { it.key })
+            .containsExactly("bubble2", "bubble1")
+            .inOrder()
+
+        // wait for idle to allow the animation to start
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+        // wait for the expansion animation to complete before interacting with the bubbles
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+            AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+
+        // make the IME visible and tap on bubble1 to select it
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            positioner.setImeVisible(true, 100)
+            bubble1.iconView!!.performClick()
+            // we have to set the selected bubble in the stack view manually because we don't have a
+            // listener wired up.
+            bubbleStackView.setSelectedBubble(bubble1)
+            shellExecutor.flushAll()
+        }
+
+        val onImeHidden = bubbleStackViewManager.onImeHidden
+        assertThat(onImeHidden).isNotNull()
+
+        assertThat(expandListener.bubblesExpandedState).isEqualTo(mapOf("bubble2" to true))
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            onImeHidden!!.run()
+            shellExecutor.flushAll()
+        }
+
+        assertThat(expandListener.bubblesExpandedState)
+            .isEqualTo(mapOf("bubble1" to true, "bubble2" to false))
+        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+        assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+    }
+
+    @Test
+    fun tapDifferentBubble_imeHidden_updatesImmediately() {
+        val bubble1 = createAndInflateChatBubble(key = "bubble1")
+        val bubble2 = createAndInflateChatBubble(key = "bubble2")
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubbleStackView.addBubble(bubble1)
+            bubbleStackView.addBubble(bubble2)
+        }
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+        assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
+        assertThat(bubbleData.bubbles).hasSize(2)
+        assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
+        assertThat(bubble2.iconView).isNotNull()
+
+        val expandListener = FakeBubbleExpandListener()
+        bubbleStackView.setExpandListener(expandListener)
+
+        var lastUpdate: BubbleData.Update? = null
+        val semaphore = Semaphore(0)
+        val listener =
+            BubbleData.Listener { update ->
+                lastUpdate = update
+                semaphore.release()
+            }
+        bubbleData.setListener(listener)
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubble2.iconView!!.performClick()
+            assertThat(bubbleData.isExpanded).isTrue()
+
+            bubbleStackView.setSelectedBubble(bubble2)
+            bubbleStackView.isExpanded = true
+            shellExecutor.flushAll()
+        }
+
+        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+        assertThat(lastUpdate!!.expanded).isTrue()
+        assertThat(lastUpdate!!.bubbles.map { it.key })
+            .containsExactly("bubble2", "bubble1")
+            .inOrder()
+
+        // wait for idle to allow the animation to start
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+        // wait for the expansion animation to complete before interacting with the bubbles
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+            AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+
+        // make the IME hidden and tap on bubble1 to select it
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            positioner.setImeVisible(false, 0)
+            bubble1.iconView!!.performClick()
+            // we have to set the selected bubble in the stack view manually because we don't have a
+            // listener wired up.
+            bubbleStackView.setSelectedBubble(bubble1)
+            shellExecutor.flushAll()
+        }
+
+        val onImeHidden = bubbleStackViewManager.onImeHidden
+        assertThat(onImeHidden).isNull()
+
+        assertThat(expandListener.bubblesExpandedState)
+            .isEqualTo(mapOf("bubble1" to true, "bubble2" to false))
+        assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+        assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+    }
+
     @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
     @Test
     fun testCreateStackView_noOverflowContents_noOverflow() {
@@ -563,4 +709,18 @@
             this.onImeHidden = onImeHidden
         }
     }
+
+    private class FakeBubbleExpandListener : BubbleExpandListener {
+        val bubblesExpandedState = mutableMapOf<String, Boolean>()
+        override fun onBubbleExpandChanged(isExpanding: Boolean, key: String) {
+            bubblesExpandedState[key] = isExpanding
+        }
+    }
+
+    private class FakeSurfaceSynchronizer : SurfaceSynchronizer {
+        var isActive = true
+        override fun syncSurfaceAndRun(callback: Runnable) {
+            if (isActive) callback.run()
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
index a83327b..f1ba042 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
@@ -49,6 +49,7 @@
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewRepository
 import com.android.wm.shell.taskview.TaskViewTransitions
 import com.android.wm.shell.transition.Transitions
 import com.google.common.truth.Truth.assertThat
@@ -155,6 +156,7 @@
                 mainExecutor,
                 mock<Handler>(),
                 bgExecutor,
+                mock<TaskViewRepository>(),
                 mock<TaskViewTransitions>(),
                 mock<Transitions>(),
                 SyncTransactionQueue(TransactionPool(), mainExecutor),
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt
index 42b66aa..896f2ee 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewController
 import com.android.wm.shell.taskview.TaskViewTaskController
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
@@ -33,7 +34,7 @@
 ) : BubbleTaskViewFactory {
     override fun create(): BubbleTaskView {
         val taskViewTaskController = mock<TaskViewTaskController>()
-        val taskView = TaskView(context, taskViewTaskController)
+        val taskView = TaskView(context, mock<TaskViewController>(), taskViewTaskController)
         val taskInfo = mock<ActivityManager.RunningTaskInfo>()
         whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
         return BubbleTaskView(taskView, mainExecutor)
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
index 9e58b5b..d3cfbd0 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
@@ -47,6 +47,7 @@
 import com.android.wm.shell.bubbles.FakeBubbleFactory
 import com.android.wm.shell.common.TestShellExecutor
 import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewController
 import com.android.wm.shell.taskview.TaskViewTaskController
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Semaphore
@@ -58,6 +59,7 @@
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
 import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -165,7 +167,9 @@
     fun animateSwitch_bubbleToBubble_updateTaskBounds() {
         val fromBubble = createBubble("from").initialize(container)
         val toBubbleTaskController = mock<TaskViewTaskController>()
-        val toBubble = createBubble("to", toBubbleTaskController).initialize(container)
+        val taskController = mock<TaskViewController>()
+        val toBubble = createBubble("to", taskController, toBubbleTaskController).initialize(
+            container)
 
         activityScenario.onActivity {
             animationHelper.animateSwitch(fromBubble, toBubble) {}
@@ -174,11 +178,11 @@
         }
         getInstrumentation().waitForIdleSync()
         // Clear invocations to ensure that bounds update happens after animation ends
-        clearInvocations(toBubbleTaskController)
+        clearInvocations(taskController)
         getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(900) }
         getInstrumentation().waitForIdleSync()
 
-        verify(toBubbleTaskController).setWindowBounds(any())
+        verify(taskController).setTaskBounds(eq(toBubbleTaskController), any())
     }
 
     @Test
@@ -229,8 +233,9 @@
 
     @Test
     fun animateToRestPosition_updateTaskBounds() {
-        val taskController = mock<TaskViewTaskController>()
-        val bubble = createBubble("key", taskController).initialize(container)
+        val taskView = mock<TaskViewTaskController>()
+        val controller = mock<TaskViewController>()
+        val bubble = createBubble("key", controller, taskView).initialize(container)
 
         val semaphore = Semaphore(0)
         val after = Runnable { semaphore.release() }
@@ -247,11 +252,11 @@
             animatorTestRule.advanceTimeBy(100)
         }
         // Clear invocations to ensure that bounds update happens after animation ends
-        clearInvocations(taskController)
+        clearInvocations(controller)
         getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(900) }
         getInstrumentation().waitForIdleSync()
 
-        verify(taskController).setWindowBounds(any())
+        verify(controller).setTaskBounds(eq(taskView), any())
     }
 
     @Test
@@ -329,9 +334,10 @@
 
     private fun createBubble(
         key: String,
+        taskViewController: TaskViewController = mock<TaskViewController>(),
         taskViewTaskController: TaskViewTaskController = mock<TaskViewTaskController>(),
     ): Bubble {
-        val taskView = TaskView(context, taskViewTaskController)
+        val taskView = TaskView(context, taskViewController, taskViewTaskController)
         val taskInfo = mock<ActivityManager.RunningTaskInfo>()
         whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
         val bubbleTaskView = BubbleTaskView(taskView, mainExecutor)
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index fbbcff2..7f65e22 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -46,6 +46,7 @@
 import com.android.wm.shell.common.TestShellExecutor
 import com.android.wm.shell.shared.handles.RegionSamplingHelper
 import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewController
 import com.android.wm.shell.taskview.TaskViewTaskController
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -356,7 +357,7 @@
     private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
         override fun create(): BubbleTaskView {
             val taskViewTaskController = mock<TaskViewTaskController>()
-            val taskView = TaskView(context, taskViewTaskController)
+            val taskView = TaskView(context, mock<TaskViewController>(), taskViewTaskController)
             val taskInfo = mock<ActivityManager.RunningTaskInfo>()
             whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
             return BubbleTaskView(taskView, mainExecutor)
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 5c5dde7..a649247 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -64,6 +64,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskViewRepository
 import com.android.wm.shell.taskview.TaskViewTransitions
 import com.android.wm.shell.transition.Transitions
 import com.google.common.truth.Truth.assertThat
@@ -194,6 +195,7 @@
             mainExecutor,
             mock<Handler>(),
             bgExecutor,
+            mock<TaskViewRepository>(),
             mock<TaskViewTransitions>(),
             mock<Transitions>(),
             SyncTransactionQueue(TransactionPool(), mainExecutor),
diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
index e5fe1b54..83a3959 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
@@ -22,5 +22,6 @@
     android:viewportWidth="960">
     <path
         android:fillColor="@android:color/black"
-        android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,320L160,320L160,720Q160,720 160,720Q160,720 160,720Z"/>
+        android:pathData="M 244.79 796.408 C 222.79 796.408 203.79 788.74 187.79 773.408 C 172.457 757.408 164.79 738.408 164.79 716.408 L 164.79 236.408 C 164.79 214.408 172.457 195.741 187.79 180.408 C 203.79 164.408 222.79 156.408 244.79 156.408 L 724.79 156.408 C 746.79 156.408 765.458 164.408 780.79 180.408 C 796.79 195.741 804.79 214.408 804.79 236.408 L 804.79 716.408 C 804.79 738.408 796.79 757.408 780.79 773.408 C 765.458 788.74 746.79 796.408 724.79 796.408 Z M 244.79 716.408 L 724.79 716.408 L 724.79 236.408 L 244.79 236.408 Z M 244.79 236.408 L 244.79 716.408 Z"
+        />
 </vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/Android.bp b/libs/WindowManager/Shell/shared/Android.bp
index d7669ed..0974930 100644
--- a/libs/WindowManager/Shell/shared/Android.bp
+++ b/libs/WindowManager/Shell/shared/Android.bp
@@ -78,3 +78,17 @@
         "com.android.window.flags.window-aconfig-java",
     ],
 }
+
+// Things that can be shared with launcher3
+java_library {
+    name: "WindowManager-Shell-shared-AOSP",
+
+    sdk_version: "current",
+
+    srcs: [
+        "src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java",
+    ],
+    static_libs: [
+        "com_android_wm_shell_flags_lib",
+    ],
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 08e3692..30d6790 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -117,6 +117,8 @@
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewController;
+import com.android.wm.shell.taskview.TaskViewRepository;
 import com.android.wm.shell.taskview.TaskViewTaskController;
 import com.android.wm.shell.taskview.TaskViewTransitions;
 import com.android.wm.shell.transition.Transitions;
@@ -192,7 +194,7 @@
     private final TaskStackListenerImpl mTaskStackListener;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final DisplayController mDisplayController;
-    private final TaskViewTransitions mTaskViewTransitions;
+    private final TaskViewController mTaskViewController;
     private final Transitions mTransitions;
     private final SyncTransactionQueue mSyncQueue;
     private final ShellController mShellController;
@@ -309,6 +311,7 @@
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
             @ShellBackgroundThread ShellExecutor bgExecutor,
+            TaskViewRepository taskViewRepository,
             TaskViewTransitions taskViewTransitions,
             Transitions transitions,
             SyncTransactionQueue syncQueue,
@@ -347,7 +350,12 @@
                 context.getResources().getDimensionPixelSize(
                         com.android.internal.R.dimen.importance_ring_stroke_width));
         mDisplayController = displayController;
-        mTaskViewTransitions = taskViewTransitions;
+        if (TaskViewTransitions.useRepo()) {
+            mTaskViewController = new TaskViewTransitions(transitions, taskViewRepository,
+                    organizer, syncQueue);
+        } else {
+            mTaskViewController = taskViewTransitions;
+        }
         mTransitions = transitions;
         mOneHandedOptional = oneHandedOptional;
         mDragAndDropController = dragAndDropController;
@@ -359,8 +367,9 @@
             @Override
             public BubbleTaskView create() {
                 TaskViewTaskController taskViewTaskController = new TaskViewTaskController(
-                        context, organizer, taskViewTransitions, syncQueue);
-                TaskView taskView = new TaskView(context, taskViewTaskController);
+                        context, organizer, mTaskViewController, syncQueue);
+                TaskView taskView = new TaskView(context, mTaskViewController,
+                        taskViewTaskController);
                 return new BubbleTaskView(taskView, mainExecutor);
             }
         };
@@ -843,14 +852,6 @@
         return mTaskOrganizer;
     }
 
-    SyncTransactionQueue getSyncTransactionQueue() {
-        return mSyncQueue;
-    }
-
-    TaskViewTransitions getTaskViewTransitions() {
-        return mTaskViewTransitions;
-    }
-
     /** Contains information to help position things on the screen. */
     @VisibleForTesting
     public BubblePositioner getPositioner() {
@@ -1439,9 +1440,9 @@
      *
      * @param intent the intent for the bubble.
      */
-    public void expandStackAndSelectBubble(Intent intent) {
+    public void expandStackAndSelectBubble(Intent intent, UserHandle user) {
         if (!Flags.enableBubbleAnything()) return;
-        Bubble b = mBubbleData.getOrCreateBubble(intent); // Removes from overflow
+        Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow
         ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent);
         if (b.isInflated()) {
             mBubbleData.setSelectedBubbleAndExpandStack(b);
@@ -2648,8 +2649,8 @@
         }
 
         @Override
-        public void showAppBubble(Intent intent) {
-            mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent));
+        public void showAppBubble(Intent intent, UserHandle user) {
+            mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent, user));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index dc2025b..76d91ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -461,10 +461,8 @@
         return bubbleToReturn;
     }
 
-    Bubble getOrCreateBubble(Intent intent) {
-        UserHandle user = UserHandle.of(mCurrentUserId);
-        String bubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(),
-                user);
+    Bubble getOrCreateBubble(Intent intent, UserHandle user) {
+        String bubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
         Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey);
         if (bubbleToReturn == null) {
             bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor, mBgExecutor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 979d958..1094c29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -2183,34 +2183,39 @@
         ProtoLog.d(WM_SHELL_BUBBLES, "showNewlySelectedBubble b=%s, previouslySelected=%s,"
                         + " mIsExpanded=%b", newlySelectedKey, previouslySelectedKey, mIsExpanded);
         if (mIsExpanded) {
-            hideCurrentInputMethod();
-
-            if (Flags.enableRetrievableBubbles()) {
-                if (mBubbleData.getBubbles().size() == 1) {
-                    // First bubble, check if overflow visibility needs to change
-                    updateOverflowVisibility();
+            Runnable onImeHidden = () -> {
+                if (Flags.enableRetrievableBubbles()) {
+                    if (mBubbleData.getBubbles().size() == 1) {
+                        // First bubble, check if overflow visibility needs to change
+                        updateOverflowVisibility();
+                    }
                 }
+
+                // Make the container of the expanded view transparent before removing the expanded
+                // view from it. Otherwise a punch hole created by {@link android.view.SurfaceView}
+                // in the expanded view becomes visible on the screen. See b/126856255
+                mExpandedViewContainer.setAlpha(0.0f);
+                mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
+                    if (previouslySelected != null) {
+                        previouslySelected.setTaskViewVisibility(false);
+                    }
+
+                    updateExpandedBubble();
+                    requestUpdate();
+
+                    logBubbleEvent(previouslySelected,
+                            FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
+                    logBubbleEvent(bubbleToSelect,
+                            FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
+                    notifyExpansionChanged(previouslySelected, false /* expanded */);
+                    notifyExpansionChanged(bubbleToSelect, true /* expanded */);
+                });
+            };
+            if (mPositioner.isImeVisible()) {
+                hideCurrentInputMethod(onImeHidden);
+            } else {
+                onImeHidden.run();
             }
-
-            // Make the container of the expanded view transparent before removing the expanded view
-            // from it. Otherwise a punch hole created by {@link android.view.SurfaceView} in the
-            // expanded view becomes visible on the screen. See b/126856255
-            mExpandedViewContainer.setAlpha(0.0f);
-            mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
-                if (previouslySelected != null) {
-                    previouslySelected.setTaskViewVisibility(false);
-                }
-
-                updateExpandedBubble();
-                requestUpdate();
-
-                logBubbleEvent(previouslySelected,
-                        FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
-                logBubbleEvent(bubbleToSelect,
-                        FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
-                notifyExpansionChanged(previouslySelected, false /* expanded */);
-                notifyExpansionChanged(bubbleToSelect, true /* expanded */);
-            });
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 9c2d3543..0a4d79a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -17,8 +17,9 @@
 package com.android.wm.shell.bubbles;
 
 import android.content.Intent;
-import android.graphics.Rect;
 import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.os.UserHandle;
 import com.android.wm.shell.bubbles.IBubblesListener;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 
@@ -52,7 +53,7 @@
 
     oneway void showShortcutBubble(in ShortcutInfo info) = 12;
 
-    oneway void showAppBubble(in Intent intent) = 13;
+    oneway void showAppBubble(in Intent intent, in UserHandle user) = 13;
 
     oneway void showExpandedView() = 14;
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 72be066..e69d60d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayTopology;
 import android.os.RemoteException;
@@ -41,7 +42,9 @@
 import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -62,6 +65,7 @@
 
     private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
     private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
+    private final Map<Integer, RectF> mUnpopulatedDisplayBounds = new HashMap<>();
 
     public DisplayController(Context context, IWindowManager wmService, ShellInit shellInit,
             ShellExecutor mainExecutor, DisplayManager displayManager) {
@@ -193,7 +197,12 @@
                     ? mContext
                     : mContext.createDisplayContext(display);
             final DisplayRecord record = new DisplayRecord(displayId);
-            record.setDisplayLayout(context, new DisplayLayout(context, display));
+            DisplayLayout displayLayout = new DisplayLayout(context, display);
+            if (Flags.enableConnectedDisplaysWindowDrag()
+                    && mUnpopulatedDisplayBounds.containsKey(displayId)) {
+                displayLayout.setGlobalBoundsDp(mUnpopulatedDisplayBounds.get(displayId));
+            }
+            record.setDisplayLayout(context, displayLayout);
             mDisplays.put(displayId, record);
             for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
                 mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
@@ -231,10 +240,27 @@
     }
 
     private void onDisplayTopologyChanged(DisplayTopology topology) {
-        // TODO(b/381472611): Call DisplayTopology#getCoordinates and update values in
-        //                    DisplayLayout when DM code is ready.
+        if (topology == null) {
+            return;
+        }
+        SparseArray<RectF> absoluteBounds = topology.getAbsoluteBounds();
+        mUnpopulatedDisplayBounds.clear();
+        for (int i = 0; i < absoluteBounds.size(); ++i) {
+            int displayId = absoluteBounds.keyAt(i);
+            DisplayLayout displayLayout = getDisplayLayout(displayId);
+            if (displayLayout == null) {
+                // onDisplayTopologyChanged can arrive before onDisplayAdded.
+                // Store the bounds to be applied later in onDisplayAdded.
+                Slog.d(TAG, "Storing bounds for onDisplayTopologyChanged on unknown"
+                        + " display, displayId=" + displayId);
+                mUnpopulatedDisplayBounds.put(displayId, absoluteBounds.valueAt(i));
+            } else {
+                displayLayout.setGlobalBoundsDp(absoluteBounds.valueAt(i));
+            }
+        }
+
         for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
-            mDisplayChangedListeners.get(i).onTopologyChanged();
+            mDisplayChangedListeners.get(i).onTopologyChanged(topology);
         }
     }
 
@@ -429,6 +455,6 @@
         /**
          * Called when the display topology has changed.
          */
-        default void onTopologyChanged() {}
+        default void onTopologyChanged(DisplayTopology topology) {}
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index bcd40a9..c4696d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -192,15 +192,22 @@
                 throw new IllegalStateException("Sync Transactions must be serialized. In Flight: "
                         + mInFlight.mId + " - " + mInFlight.mWCT);
             }
-            mInFlight = this;
             if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT);
-            if (mLegacyTransition != null) {
-                mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(),
-                        mLegacyTransition.getAdapter(), this, mWCT);
-            } else {
-                mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
+            try {
+                if (mLegacyTransition != null) {
+                    mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(),
+                            mLegacyTransition.getAdapter(), this, mWCT);
+                } else {
+                    mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
+                }
+            } catch (RuntimeException e) {
+                Slog.e(TAG, "Send failed", e);
+                // Finish current sync callback immediately.
+                onTransactionReady(mId, new SurfaceControl.Transaction());
+                return;
             }
             if (DEBUG) Slog.d(TAG, " Sent sync transaction. Got id=" + mId);
+            mInFlight = this;
             mMainExecutor.executeDelayed(mOnReplyTimeout, REPLY_TIMEOUT);
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
index e779879..3777907 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.common.pip;
 
+import android.app.ActivityManager;
 import android.app.PictureInPictureParams;
 import android.view.SurfaceControl;
 import android.content.ComponentName;
@@ -41,9 +42,8 @@
               bounds
      * @return destination bounds the PiP window should land into
      */
-    Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo,
-                in PictureInPictureParams pictureInPictureParams,
-                int launcherRotation, in Rect hotseatKeepClearArea) = 1;
+    Rect startSwipePipToHome(in ActivityManager.RunningTaskInfo taskInfo, int launcherRotation,
+            in Rect hotseatKeepClearArea) = 1;
 
     /**
      * Notifies the swiping Activity to PiP onto home transition is finished
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
index 85353d3..bad4a93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
@@ -18,7 +18,6 @@
 
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -111,7 +110,7 @@
             int width, int height) {
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
                 TYPE_APPLICATION_OVERLAY,
-                FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
+                FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY;
         lp.setTitle(title);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 89573cc..84b710d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -19,7 +19,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
@@ -126,7 +125,7 @@
         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER,
                 FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
-                        | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
+                        | FLAG_SLIPPERY,
                 PixelFormat.TRANSLUCENT);
         lp.token = new Binder();
         lp.setTitle(mWindowName);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index cbbe8a2..8404259 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -771,8 +771,9 @@
     @WMSingleton
     @Provides
     static TaskViewTransitions provideTaskViewTransitions(Transitions transitions,
-            TaskViewRepository repository) {
-        return new TaskViewTransitions(transitions, repository);
+            TaskViewRepository repository, ShellTaskOrganizer organizer,
+            SyncTransactionQueue syncQueue) {
+        return new TaskViewTransitions(transitions, repository, organizer, syncQueue);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index e8add56..67e3453 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -133,6 +133,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskViewRepository;
 import com.android.wm.shell.taskview.TaskViewTransitions;
 import com.android.wm.shell.transition.DefaultMixedHandler;
 import com.android.wm.shell.transition.FocusTransitionObserver;
@@ -247,6 +248,7 @@
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
             @ShellBackgroundThread ShellExecutor bgExecutor,
+            TaskViewRepository taskViewRepository,
             TaskViewTransitions taskViewTransitions,
             Transitions transitions,
             SyncTransactionQueue syncQueue,
@@ -280,6 +282,7 @@
                 mainExecutor,
                 mainHandler,
                 bgExecutor,
+                taskViewRepository,
                 taskViewTransitions,
                 transitions,
                 syncQueue,
@@ -1028,8 +1031,9 @@
     static CloseDesktopTaskTransitionHandler provideCloseDesktopTaskTransitionHandler(
             Context context,
             @ShellMainThread ShellExecutor mainExecutor,
-            @ShellAnimationThread ShellExecutor animExecutor) {
-        return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor);
+            @ShellAnimationThread ShellExecutor animExecutor,
+            @ShellMainThread Handler handler) {
+        return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor, handler);
     }
 
     @WMSingleton
@@ -1154,9 +1158,12 @@
             Context context,
             ShellInit shellInit,
             Transitions transitions,
-            DesktopModeEventLogger desktopModeEventLogger) {
+            DesktopModeEventLogger desktopModeEventLogger,
+            Optional<DesktopTasksLimiter> desktopTasksLimiter,
+            ShellTaskOrganizer shellTaskOrganizer) {
         return new DesktopModeLoggerTransitionObserver(
-                context, shellInit, transitions, desktopModeEventLogger);
+                context, shellInit, transitions, desktopModeEventLogger,
+                desktopTasksLimiter, shellTaskOrganizer);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index c8d0dab..793bdf0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -83,13 +83,14 @@
             @NonNull PipTransitionState pipStackListenerController,
             @NonNull PipDisplayLayoutState pipDisplayLayoutState,
             @NonNull PipUiStateChangeController pipUiStateChangeController,
+            DisplayController displayController,
             Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
             Optional<DesktopWallpaperActivityTokenProvider>
                     desktopWallpaperActivityTokenProviderOptional) {
         return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
                 pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
                 pipScheduler, pipStackListenerController, pipDisplayLayoutState,
-                pipUiStateChangeController, desktopUserRepositoriesOptional,
+                pipUiStateChangeController, displayController, desktopUserRepositoriesOptional,
                 desktopWallpaperActivityTokenProviderOptional);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
index 9b5a289..1ce093e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
@@ -23,8 +23,10 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.Context
 import android.graphics.Rect
+import android.os.Handler
 import android.os.IBinder
 import android.util.TypedValue
+import android.view.Choreographer
 import android.view.SurfaceControl.Transaction
 import android.view.WindowManager
 import android.window.TransitionInfo
@@ -32,7 +34,10 @@
 import android.window.WindowContainerTransaction
 import androidx.core.animation.addListener
 import com.android.app.animation.Interpolators
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_CLOSE_TASK
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.annotations.ShellMainThread
 import com.android.wm.shell.transition.Transitions
 import java.util.function.Supplier
 
@@ -44,9 +49,11 @@
     private val mainExecutor: ShellExecutor,
     private val animExecutor: ShellExecutor,
     private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
+    @ShellMainThread private val handler: Handler,
 ) : Transitions.TransitionHandler {
 
     private val runningAnimations = mutableMapOf<IBinder, List<Animator>>()
+    private val interactionJankMonitor = InteractionJankMonitor.getInstance()
 
     /** Returns null, as it only handles transitions started from Shell. */
     override fun handleRequest(
@@ -71,18 +78,27 @@
                     // All animations completed, finish the transition
                     runningAnimations.remove(transition)
                     finishCallback.onTransitionFinished(/* wct= */ null)
+                    interactionJankMonitor.end(CUJ_DESKTOP_MODE_CLOSE_TASK)
                 }
             }
         }
+        val closingChanges =
+            info.changes.filter {
+                it.mode == WindowManager.TRANSIT_CLOSE &&
+                    it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+            }
         animations +=
-            info.changes
-                .filter {
-                    it.mode == WindowManager.TRANSIT_CLOSE &&
-                        it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
-                }
-                .map { createCloseAnimation(it, finishTransaction, onAnimFinish) }
+            closingChanges.map { createCloseAnimation(it, finishTransaction, onAnimFinish) }
         if (animations.isEmpty()) return false
         runningAnimations[transition] = animations
+        closingChanges.lastOrNull()?.leash?.let { lastChangeLeash ->
+            interactionJankMonitor.begin(
+                lastChangeLeash,
+                context,
+                handler,
+                CUJ_DESKTOP_MODE_CLOSE_TASK,
+            )
+        }
         animExecutor.execute { animations.forEach(Animator::start) }
         return true
     }
@@ -127,6 +143,7 @@
                     .get()
                     .setPosition(change.leash, animBounds.left.toFloat(), animBounds.top.toFloat())
                     .setScale(change.leash, animScale, animScale)
+                    .setFrameTimeline(Choreographer.getInstance().vsyncId)
                     .apply()
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index e8f9a78..68bdbd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -467,9 +467,13 @@
                 FrameworkStatsLog
                     .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT
             ),
-            MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value
+            MINIMIZE_BUTTON(
                 FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON
             ),
+            KEY_GESTURE(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_KEY_GESTURE
+            ),
         }
 
         // Default value used when the task was not unminimized.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
index 1ddb834..9334898 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
@@ -30,6 +30,7 @@
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
 import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -125,7 +126,9 @@
             KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> {
                 logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled")
                 getGloballyFocusedFreeformTask()?.let {
-                    mainExecutor.execute { desktopTasksController.get().minimizeTask(it) }
+                    mainExecutor.execute {
+                        desktopTasksController.get().minimizeTask(it, MinimizeReason.KEY_GESTURE)
+                    }
                 }
                 return true
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index c09504e..2dd89c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -36,6 +36,7 @@
 import androidx.core.util.plus
 import androidx.core.util.putAll
 import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
@@ -52,6 +53,8 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
 
 /**
  * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
@@ -63,6 +66,8 @@
     shellInit: ShellInit,
     private val transitions: Transitions,
     private val desktopModeEventLogger: DesktopModeEventLogger,
+    private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
+    private val shellTaskOrganizer: ShellTaskOrganizer,
 ) : Transitions.TransitionObserver {
 
     init {
@@ -141,6 +146,7 @@
 
         // identify if we need to log any changes and update the state of visible freeform tasks
         identifyLogEventAndUpdateState(
+            transition = transition,
             transitionInfo = info,
             preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
             postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks,
@@ -227,6 +233,7 @@
      * state and update it
      */
     private fun identifyLogEventAndUpdateState(
+        transition: IBinder,
         transitionInfo: TransitionInfo,
         preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
         postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
@@ -238,6 +245,7 @@
         ) {
             // Sessions is finishing, log task updates followed by an exit event
             identifyAndLogTaskUpdates(
+                transition,
                 transitionInfo,
                 preTransitionVisibleFreeformTasks,
                 postTransitionVisibleFreeformTasks,
@@ -255,6 +263,7 @@
             desktopModeEventLogger.logSessionEnter(getEnterReason(transitionInfo))
 
             identifyAndLogTaskUpdates(
+                transition,
                 transitionInfo,
                 preTransitionVisibleFreeformTasks,
                 postTransitionVisibleFreeformTasks,
@@ -262,6 +271,7 @@
         } else if (isSessionActive) {
             // Session is neither starting, nor finishing, log task updates if there are any
             identifyAndLogTaskUpdates(
+                transition,
                 transitionInfo,
                 preTransitionVisibleFreeformTasks,
                 postTransitionVisibleFreeformTasks,
@@ -275,6 +285,7 @@
 
     /** Compare the old and new state of taskInfos and identify and log the changes */
     private fun identifyAndLogTaskUpdates(
+        transition: IBinder,
         transitionInfo: TransitionInfo,
         preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
         postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
@@ -310,12 +321,9 @@
         // find old tasks that were removed
         preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
             if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
-                val minimizeReason =
-                    if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) {
-                        MinimizeReason.MINIMIZE_BUTTON
-                    } else {
-                        null
-                    }
+                // The task is no longer visible, it might have been minimized, get the minimize
+                // reason (if any)
+                val minimizeReason = getMinimizeReason(transition, transitionInfo, taskInfo)
                 val taskUpdate =
                     buildTaskUpdateForTask(
                         taskInfo,
@@ -336,6 +344,21 @@
         }
     }
 
+    private fun getMinimizeReason(
+        transition: IBinder,
+        transitionInfo: TransitionInfo,
+        taskInfo: TaskInfo,
+    ): MinimizeReason? {
+        if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) {
+            return MinimizeReason.MINIMIZE_BUTTON
+        }
+        val minimizingTask = desktopTasksLimiter.getOrNull()?.getMinimizingTask(transition)
+        if (minimizingTask?.taskId == taskInfo.taskId) {
+            return minimizingTask.minimizeReason
+        }
+        return null
+    }
+
     private fun buildTaskUpdateForTask(
         taskInfo: TaskInfo,
         visibleTasks: Int,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index c975533..fa69668 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -24,8 +24,8 @@
 import android.view.Display.INVALID_DISPLAY
 import android.window.DesktopModeFlags
 import androidx.core.util.forEach
-import androidx.core.util.keyIterator
 import androidx.core.util.valueIterator
+import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.protolog.ProtoLog
 import com.android.window.flags.Flags
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
@@ -43,26 +43,36 @@
     @ShellMainThread private val mainCoroutineScope: CoroutineScope,
     val userId: Int,
 ) {
+    /** A display that supports desktops. */
+    private data class DesktopDisplay(
+        val displayId: Int,
+        val orderedDesks: MutableSet<Desk> = mutableSetOf(),
+        // TODO: b/389960283 - update on desk activation / deactivation.
+        var activeDeskId: Int? = null,
+    )
+
     /**
-     * Task data tracked per desktop.
+     * Task data tracked per desk.
      *
-     * @property activeTasks task ids of active tasks currently or previously visible in Desktop
-     *   mode session. Tasks become inactive when task closes or when desktop mode session ends.
+     * @property activeTasks task ids of active tasks currently or previously visible in the desk.
+     *   Tasks become inactive when task closes or when the desk becomes inactive.
      * @property visibleTasks task ids for active freeform tasks that are currently visible. There
-     *   might be other active tasks in desktop mode that are not visible.
+     *   might be other active tasks in a desk that are not visible.
      * @property minimizedTasks task ids for active freeform tasks that are currently minimized.
      * @property closingTasks task ids for tasks that are going to close, but are currently visible.
      * @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom
-     * @property fullImmersiveTaskId the task id of the desktop task that is in full-immersive mode.
+     * @property fullImmersiveTaskId the task id of the desk's task that is in full-immersive mode.
      * @property topTransparentFullscreenTaskId the task id of any current top transparent
-     *   fullscreen task launched on top of Desktop Mode. Cleared when the transparent task is
-     *   closed or sent to back. (top is at index 0).
+     *   fullscreen task launched on top of the desk. Cleared when the transparent task is closed or
+     *   sent to back. (top is at index 0).
      * @property pipTaskId the task id of PiP task entered while in Desktop Mode.
-     * @property pipShouldKeepDesktopActive whether an active PiP window should keep the Desktop
-     *   Mode session active. Only false when we are explicitly exiting Desktop Mode (via user
-     *   action) while there is an active PiP window.
+     * @property pipShouldKeepDesktopActive whether an active PiP window should keep the desk
+     *   active. Only false when we are explicitly exiting Desktop Mode (via user action) while
+     *   there is an active PiP window.
      */
-    private data class DesktopTaskData(
+    private data class Desk(
+        val deskId: Int,
+        val displayId: Int,
         val activeTasks: ArraySet<Int> = ArraySet(),
         val visibleTasks: ArraySet<Int> = ArraySet(),
         val minimizedTasks: ArraySet<Int> = ArraySet(),
@@ -72,10 +82,13 @@
         var fullImmersiveTaskId: Int? = null,
         var topTransparentFullscreenTaskId: Int? = null,
         var pipTaskId: Int? = null,
+        // TODO: b/389960283 - consolidate this with [DesktopDisplay#activeDeskId].
         var pipShouldKeepDesktopActive: Boolean = true,
     ) {
-        fun deepCopy(): DesktopTaskData =
-            DesktopTaskData(
+        fun deepCopy(): Desk =
+            Desk(
+                deskId = deskId,
+                displayId = displayId,
                 activeTasks = ArraySet(activeTasks),
                 visibleTasks = ArraySet(visibleTasks),
                 minimizedTasks = ArraySet(minimizedTasks),
@@ -87,6 +100,8 @@
                 pipShouldKeepDesktopActive = pipShouldKeepDesktopActive,
             )
 
+        // TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't
+        //  reusable.
         fun clear() {
             activeTasks.clear()
             visibleTasks.clear()
@@ -121,11 +136,11 @@
     private var desktopGestureExclusionListener: Consumer<Region>? = null
     private var desktopGestureExclusionExecutor: Executor? = null
 
-    private val desktopTaskDataByDisplayId =
-        object : SparseArray<DesktopTaskData>() {
-            /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */
-            fun getOrCreate(displayId: Int): DesktopTaskData =
-                this[displayId] ?: DesktopTaskData().also { this[displayId] = it }
+    private val desktopData: DesktopData =
+        if (Flags.enableMultipleDesktopsBackend()) {
+            MultiDesktopData()
+        } else {
+            SingleDesktopData()
         }
 
     /** Adds [activeTasksListener] to be notified of updates to active tasks. */
@@ -136,10 +151,16 @@
     /** Adds [visibleTasksListener] to be notified of updates to visible tasks. */
     fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
         visibleTasksListeners[visibleTasksListener] = executor
-        desktopTaskDataByDisplayId.keyIterator().forEach {
-            val visibleTaskCount = getVisibleTaskCount(it)
-            executor.execute { visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount) }
-        }
+        desktopData
+            .desksSequence()
+            .groupBy { it.displayId }
+            .keys
+            .forEach { displayId ->
+                val visibleTaskCount = getVisibleTaskCount(displayId)
+                executor.execute {
+                    visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTaskCount)
+                }
+            }
     }
 
     /** Updates tasks changes on all the active task listeners for given display id. */
@@ -147,9 +168,8 @@
         activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
     }
 
-    /** Returns a list of all [DesktopTaskData] in the repository. */
-    private fun desktopTaskDataSequence(): Sequence<DesktopTaskData> =
-        desktopTaskDataByDisplayId.valueIterator().asSequence()
+    /** Returns a list of all [Desk]s in the repository. */
+    private fun desksSequence(): Sequence<Desk> = desktopData.desksSequence()
 
     /** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */
     fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
@@ -179,99 +199,183 @@
         visibleTasksListeners.remove(visibleTasksListener)
     }
 
-    /** Adds task with [taskId] to the list of freeform tasks on [displayId]. */
+    /** Adds the given desk under the given display. */
+    fun addDesk(displayId: Int, deskId: Int) {
+        desktopData.getOrCreateDesk(displayId, deskId)
+    }
+
+    /** Returns the default desk in the given display. */
+    fun getDefaultDesk(displayId: Int): Int? = desktopData.getDefaultDesk(displayId)?.deskId
+
+    /** Sets the given desk as the active one in the given display. */
+    fun setActiveDesk(displayId: Int, deskId: Int) {
+        desktopData.setActiveDesk(displayId = displayId, deskId = deskId)
+    }
+
+    /**
+     * Adds task with [taskId] to the list of freeform tasks on [displayId]'s active desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun addTask(displayId: Int, taskId: Int, isVisible: Boolean) {
         addOrMoveFreeformTaskToTop(displayId, taskId)
         addActiveTask(displayId, taskId)
         updateTask(displayId, taskId, isVisible)
     }
 
-    /** Adds task with [taskId] to the list of active tasks on [displayId]. */
+    /**
+     * Adds task with [taskId] to the list of active tasks on [displayId]'s active desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     private fun addActiveTask(displayId: Int, taskId: Int) {
-        // Removes task if it is active on another display excluding [displayId].
-        removeActiveTask(taskId, excludedDisplayId = displayId)
+        val activeDeskId =
+            desktopData.getActiveDesk(displayId)?.deskId
+                ?: error("Expected active desk in display: $displayId")
 
-        if (desktopTaskDataByDisplayId.getOrCreate(displayId).activeTasks.add(taskId)) {
-            logD("Adds active task=%d displayId=%d", taskId, displayId)
+        // Removes task if it is active on another desk excluding [activeDesk].
+        removeActiveTask(taskId, excludedDeskId = activeDeskId)
+
+        if (desktopData.getOrCreateDesk(displayId, activeDeskId).activeTasks.add(taskId)) {
+            logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId)
             updateActiveTasksListeners(displayId)
         }
     }
 
-    /** Removes task from active task list of displays excluding the [excludedDisplayId]. */
-    fun removeActiveTask(taskId: Int, excludedDisplayId: Int? = null) {
-        desktopTaskDataByDisplayId.forEach { displayId, desktopTaskData ->
-            if ((displayId != excludedDisplayId) && desktopTaskData.activeTasks.remove(taskId)) {
-                logD("Removed active task=%d displayId=%d", taskId, displayId)
-                updateActiveTasksListeners(displayId)
+    /** Removes task from active task list of desks excluding the [excludedDeskId]. */
+    @VisibleForTesting
+    fun removeActiveTask(taskId: Int, excludedDeskId: Int? = null) {
+        val affectedDisplays = mutableSetOf<Int>()
+        desktopData.forAllDesks { displayId, desk ->
+            if (desk.deskId != excludedDeskId && desk.activeTasks.remove(taskId)) {
+                logD(
+                    "Removed active task=%d displayId=%d deskId=%d",
+                    taskId,
+                    displayId,
+                    desk.deskId,
+                )
+                affectedDisplays.add(displayId)
             }
         }
+        affectedDisplays.forEach { displayId -> updateActiveTasksListeners(displayId) }
     }
 
-    /** Adds given task to the closing task list for [displayId]. */
+    /**
+     * Adds given task to the closing task list for [displayId]'s active desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun addClosingTask(displayId: Int, taskId: Int) {
-        if (desktopTaskDataByDisplayId.getOrCreate(displayId).closingTasks.add(taskId)) {
-            logD("Added closing task=%d displayId=%d", taskId, displayId)
+        val activeDeskId =
+            desktopData.getActiveDesk(displayId)?.deskId
+                ?: error("Expected active desk in display: $displayId")
+        if (desktopData.getOrCreateDesk(displayId, activeDeskId).closingTasks.add(taskId)) {
+            logD("Added closing task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId)
         } else {
             // If the task hasn't been removed from closing list after it disappeared.
-            logW("Task with taskId=%d displayId=%d is already closing", taskId, displayId)
+            logW(
+                "Task with taskId=%d displayId=%d deskId=%d is already closing",
+                taskId,
+                displayId,
+                activeDeskId,
+            )
         }
     }
 
-    /** Removes task from the list of closing tasks for [displayId]. */
+    /** Removes task from the list of closing tasks for all desks. */
     fun removeClosingTask(taskId: Int) {
-        desktopTaskDataByDisplayId.forEach { displayId, taskInfo ->
-            if (taskInfo.closingTasks.remove(taskId)) {
-                logD("Removed closing task=%d displayId=%d", taskId, displayId)
+        desktopData.forAllDesks { desk ->
+            if (desk.closingTasks.remove(taskId)) {
+                logD("Removed closing task=%d deskId=%d", taskId, desk.deskId)
             }
         }
     }
 
-    fun isActiveTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.activeTasks }
+    fun isActiveTask(taskId: Int) = desksSequence().any { taskId in it.activeTasks }
 
-    fun isClosingTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.closingTasks }
+    fun isClosingTask(taskId: Int) = desksSequence().any { taskId in it.closingTasks }
 
-    fun isVisibleTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.visibleTasks }
+    fun isVisibleTask(taskId: Int) = desksSequence().any { taskId in it.visibleTasks }
 
-    fun isMinimizedTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.minimizedTasks }
+    fun isMinimizedTask(taskId: Int) = desksSequence().any { taskId in it.minimizedTasks }
 
-    /** Checks if a task is the only visible, non-closing, non-minimized task on its display. */
+    /**
+     * Checks if a task is the only visible, non-closing, non-minimized task on the active desk of
+     * the given display, or any display's active desk if [displayId] is [INVALID_DISPLAY].
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun isOnlyVisibleNonClosingTask(taskId: Int, displayId: Int = INVALID_DISPLAY): Boolean {
-        val seq =
+        val activeDesks =
             if (displayId != INVALID_DISPLAY) {
-                sequenceOf(desktopTaskDataByDisplayId[displayId]).filterNotNull()
+                setOfNotNull(desktopData.getActiveDesk(displayId))
             } else {
-                desktopTaskDataSequence()
+                desktopData.getAllActiveDesks()
             }
-        return seq.any {
-            it.visibleTasks.subtract(it.closingTasks).subtract(it.minimizedTasks).singleOrNull() ==
-                taskId
+        return activeDesks.any { desk ->
+            desk.visibleTasks
+                .subtract(desk.closingTasks)
+                .subtract(desk.minimizedTasks)
+                .singleOrNull() == taskId
         }
     }
 
+    /**
+     * Returns the active tasks in the given display's active desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
+    @VisibleForTesting
     fun getActiveTasks(displayId: Int): ArraySet<Int> =
-        ArraySet(desktopTaskDataByDisplayId[displayId]?.activeTasks)
+        ArraySet(desktopData.getActiveDesk(displayId)?.activeTasks)
 
+    /**
+     * Returns the minimized tasks in the given display's active desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun getMinimizedTasks(displayId: Int): ArraySet<Int> =
-        ArraySet(desktopTaskDataByDisplayId[displayId]?.minimizedTasks)
+        ArraySet(desktopData.getActiveDesk(displayId)?.minimizedTasks)
 
-    /** Returns all active non-minimized tasks for [displayId] ordered from top to bottom. */
+    /**
+     * Returns all active non-minimized tasks for [displayId] ordered from top to bottom.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun getExpandedTasksOrdered(displayId: Int): List<Int> =
         getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) }
 
-    /** Returns the count of active non-minimized tasks for [displayId]. */
+    /**
+     * Returns the count of active non-minimized tasks for [displayId].
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun getExpandedTaskCount(displayId: Int): Int {
         return getActiveTasks(displayId).count { !isMinimizedTask(it) }
     }
 
-    /** Returns a list of freeform tasks, ordered from top-bottom (top at index 0). */
+    /**
+     * Returns a list of freeform tasks, ordered from top-bottom (top at index 0).
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
+    @VisibleForTesting
     fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> =
-        ArrayList(desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder ?: emptyList())
+        ArrayList(desktopData.getActiveDesk(displayId)?.freeformTasksInZOrder ?: emptyList())
+
+    /** Returns the tasks inside the given desk. */
+    fun getActiveTaskIdsInDesk(deskId: Int): Set<Int> =
+        desktopData.getDesk(deskId)?.activeTasks?.toSet()
+            ?: run {
+                logW("getTasksInDesk: could not find desk: deskId=%d", deskId)
+                emptySet()
+            }
 
     /** Removes task from visible tasks of all displays except [excludedDisplayId]. */
     private fun removeVisibleTask(taskId: Int, excludedDisplayId: Int? = null) {
-        desktopTaskDataByDisplayId.forEach { displayId, data ->
-            if ((displayId != excludedDisplayId) && data.visibleTasks.remove(taskId)) {
-                notifyVisibleTaskListeners(displayId, data.visibleTasks.size)
+        desktopData.forAllDesks { displayId, desk ->
+            if (displayId != excludedDisplayId && desk.visibleTasks.remove(taskId)) {
+                notifyVisibleTaskListeners(displayId, desk.visibleTasks.size)
             }
         }
     }
@@ -281,6 +385,8 @@
      *
      * If task was visible on a different display with a different [displayId], removes from the set
      * of visible tasks on that display and notifies listeners.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
      */
     fun updateTask(displayId: Int, taskId: Int, isVisible: Boolean) {
         logD("updateTask taskId=%d, displayId=%d, isVisible=%b", taskId, displayId, isVisible)
@@ -295,10 +401,11 @@
         }
         val prevCount = getVisibleTaskCount(displayId)
         if (isVisible) {
-            desktopTaskDataByDisplayId.getOrCreate(displayId).visibleTasks.add(taskId)
+            desktopData.getActiveDesk(displayId)?.visibleTasks?.add(taskId)
+                ?: error("Expected non-null active desk in display $displayId")
             unminimizeTask(displayId, taskId)
         } else {
-            desktopTaskDataByDisplayId[displayId]?.visibleTasks?.remove(taskId)
+            desktopData.getActiveDesk(displayId)?.visibleTasks?.remove(taskId)
         }
         val newCount = getVisibleTaskCount(displayId)
         if (prevCount != newCount) {
@@ -316,57 +423,94 @@
         }
     }
 
-    /** Set whether the given task is the Desktop-entered PiP task in this display. */
+    /**
+     * Set whether the given task is the Desktop-entered PiP task in this display's active desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) {
-        val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId)
+        val activeDesk =
+            desktopData.getActiveDesk(displayId)
+                ?: error("Expected active desk in display: $displayId")
         if (enterPip) {
-            desktopData.pipTaskId = taskId
-            desktopData.pipShouldKeepDesktopActive = true
+            activeDesk.pipTaskId = taskId
+            activeDesk.pipShouldKeepDesktopActive = true
         } else {
-            desktopData.pipTaskId =
-                if (desktopData.pipTaskId == taskId) null
+            activeDesk.pipTaskId =
+                if (activeDesk.pipTaskId == taskId) null
                 else {
                     logW(
-                        "setTaskInPip: taskId=$taskId did not match saved taskId=${desktopData.pipTaskId}"
+                        "setTaskInPip: taskId=%d did not match saved taskId=%d",
+                        taskId,
+                        activeDesk.pipTaskId,
                     )
-                    desktopData.pipTaskId
+                    activeDesk.pipTaskId
                 }
         }
         notifyVisibleTaskListeners(displayId, getVisibleTaskCount(displayId))
     }
 
-    /** Returns whether there is a PiP that was entered/minimized from Desktop in this display. */
+    /**
+     * Returns whether there is a PiP that was entered/minimized from Desktop in this display's
+     * active desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun isMinimizedPipPresentInDisplay(displayId: Int): Boolean =
-        desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId != null
+        desktopData.getActiveDesk(displayId)?.pipTaskId != null
 
-    /** Returns whether the given task is the Desktop-entered PiP task in this display. */
+    /**
+     * Returns whether the given task is the Desktop-entered PiP task in this display's active desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean =
-        desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId == taskId
+        desktopData.getActiveDesk(displayId)?.pipTaskId == taskId
 
-    /** Returns whether Desktop session should be active in this display due to active PiP. */
+    /**
+     * Returns whether a desk should be active in this display due to active PiP.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun shouldDesktopBeActiveForPip(displayId: Int): Boolean =
         Flags.enableDesktopWindowingPip() &&
             isMinimizedPipPresentInDisplay(displayId) &&
-            desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive
+            (desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive ?: false)
 
-    /** Saves whether a PiP window should keep Desktop session active in this display. */
+    /**
+     * Saves whether a PiP window should keep Desktop session active in this display.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun setPipShouldKeepDesktopActive(displayId: Int, keepActive: Boolean) {
-        desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive = keepActive
+        desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive = keepActive
     }
 
-    /** Saves callback to handle a pending PiP transition being aborted. */
-    fun setOnPipAbortedCallback(callbackIfPipAborted: ((Int, Int) -> Unit)?) {
+    /**
+     * Saves callback to handle a pending PiP transition being aborted.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
+    fun setOnPipAbortedCallback(callbackIfPipAborted: ((displayId: Int, pipTaskId: Int) -> Unit)?) {
         onPipAbortedCallback = callbackIfPipAborted
     }
 
-    /** Invokes callback to handle a pending PiP transition with the given task id being aborted. */
+    /**
+     * Invokes callback to handle a pending PiP transition with the given task id being aborted.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun onPipAborted(displayId: Int, pipTaskId: Int) {
         onPipAbortedCallback?.invoke(displayId, pipTaskId)
     }
 
-    /** Set whether the given task is the full-immersive task in this display. */
+    /**
+     * Set whether the given task is the full-immersive task in this display's active desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun setTaskInFullImmersiveState(displayId: Int, taskId: Int, immersive: Boolean) {
-        val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId)
+        val desktopData = desktopData.getActiveDesk(displayId) ?: return
         if (immersive) {
             desktopData.fullImmersiveTaskId = taskId
         } else {
@@ -378,25 +522,41 @@
 
     /* Whether the task is in full-immersive state. */
     fun isTaskInFullImmersiveState(taskId: Int): Boolean {
-        return desktopTaskDataSequence().any { taskId == it.fullImmersiveTaskId }
+        return desksSequence().any { taskId == it.fullImmersiveTaskId }
     }
 
-    /** Returns the task that is currently in immersive mode in this display, or null. */
+    /**
+     * Returns the task that is currently in immersive mode in this display, or null.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun getTaskInFullImmersiveState(displayId: Int): Int? =
-        desktopTaskDataByDisplayId.getOrCreate(displayId).fullImmersiveTaskId
+        desktopData.getActiveDesk(displayId)?.fullImmersiveTaskId
 
-    /** Sets the top transparent fullscreen task id for a given display. */
+    /**
+     * Sets the top transparent fullscreen task id for a given display's active desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun setTopTransparentFullscreenTaskId(displayId: Int, taskId: Int) {
-        desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = taskId
+        desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = taskId
     }
 
-    /** Returns the top transparent fullscreen task id for a given display, or null. */
+    /**
+     * Returns the top transparent fullscreen task id for a given display's active desk, or null.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun getTopTransparentFullscreenTaskId(displayId: Int): Int? =
-        desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId
+        desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId
 
-    /** Clears the top transparent fullscreen task id info for a given display. */
+    /**
+     * Clears the top transparent fullscreen task id info for a given display's active desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun clearTopTransparentFullscreenTaskId(displayId: Int) {
-        desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = null
+        desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = null
     }
 
     private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
@@ -409,22 +569,35 @@
         }
     }
 
-    /** Gets number of visible freeform tasks on given [displayId] */
+    /**
+     * Gets number of visible freeform tasks on given [displayId]'s active desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun getVisibleTaskCount(displayId: Int): Int =
-        desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size
-            ?: 0.also { logD("getVisibleTaskCount=$it") }
+        (desktopData.getActiveDesk(displayId)?.visibleTasks?.size ?: 0).also {
+            logD("getVisibleTaskCount=$it")
+        }
 
     /**
      * Adds task (or moves if it already exists) to the top of the ordered list.
      *
      * Unminimizes the task if it is minimized.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
      */
     private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
-        logD("Add or move task to top: display=%d taskId=%d", taskId, displayId)
-        desktopTaskDataByDisplayId.forEach { _, value ->
-            value.freeformTasksInZOrder.remove(taskId)
-        }
-        desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
+        val activeDesk =
+            desktopData.getActiveDesk(displayId)
+                ?: error("Expected a desk to be active in display: $displayId")
+        logD(
+            "Add or move task to top: display=%d taskId=%d deskId=%d",
+            taskId,
+            displayId,
+            activeDesk.deskId,
+        )
+        desktopData.forAllDesks { _, desk -> desk.freeformTasksInZOrder.remove(taskId) }
+        activeDesk.freeformTasksInZOrder.add(0, taskId)
         // Unminimize the task if it is minimized.
         unminimizeTask(displayId, taskId)
         if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -432,7 +605,11 @@
         }
     }
 
-    /** Minimizes the task for [taskId] and [displayId] */
+    /**
+     * Minimizes the task for [taskId] and [displayId]'s active display.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
+     */
     fun minimizeTask(displayId: Int, taskId: Int) {
         if (displayId == INVALID_DISPLAY) {
             // When a task vanishes it doesn't have a displayId. Find the display of the task and
@@ -441,7 +618,8 @@
                 ?: logW("Minimize task: No display id found for task: taskId=%d", taskId)
         } else {
             logD("Minimize Task: display=%d, task=%d", displayId, taskId)
-            desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
+            desktopData.getActiveDesk(displayId)?.minimizedTasks?.add(taskId)
+                ?: logD("Minimize task: No active desk found for task: taskId=%d", taskId)
         }
         updateTask(displayId, taskId, isVisible = false)
         if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -449,26 +627,42 @@
         }
     }
 
-    /** Unminimizes the task for [taskId] and [displayId] */
+    /**
+     * Unminimizes the task for [taskId] and [displayId].
+     *
+     * TODO: b/389960283 - consider adding an explicit [deskId] argument.
+     */
     fun unminimizeTask(displayId: Int, taskId: Int) {
         logD("Unminimize Task: display=%d, task=%d", displayId, taskId)
-        desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId)
-            ?: logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
+        var removed = false
+        desktopData.forAllDesks(displayId) { desk ->
+            if (desk.minimizedTasks.remove(taskId)) {
+                removed = true
+            }
+        }
+        if (!removed) {
+            logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
+        }
     }
 
     private fun getDisplayIdForTask(taskId: Int): Int? {
-        desktopTaskDataByDisplayId.forEach { displayId, data ->
-            if (taskId in data.freeformTasksInZOrder) {
-                return displayId
+        var displayForTask: Int? = null
+        desktopData.forAllDesks { displayId, desk ->
+            if (taskId in desk.freeformTasksInZOrder) {
+                displayForTask = displayId
             }
         }
-        logW("No display id found for task: taskId=%d", taskId)
-        return null
+        if (displayForTask == null) {
+            logW("No display id found for task: taskId=%d", taskId)
+        }
+        return displayForTask
     }
 
     /**
      * Removes [taskId] from the respective display. If [INVALID_DISPLAY], the original display id
      * will be looked up from the task id.
+     *
+     * TODO: b/389960283 - consider adding an explicit [deskId] argument.
      */
     fun removeTask(displayId: Int, taskId: Int) {
         logD("Removes freeform task: taskId=%d", taskId)
@@ -483,13 +677,17 @@
     /** Removes given task from a valid [displayId] and updates the repository state. */
     private fun removeTaskFromDisplay(displayId: Int, taskId: Int) {
         logD("Removes freeform task: taskId=%d, displayId=%d", taskId, displayId)
-        desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
+        desktopData.forAllDesks(displayId) { desk ->
+            if (desk.freeformTasksInZOrder.remove(taskId)) {
+                logD(
+                    "Remaining freeform tasks in desk: %d, tasks: %s",
+                    desk.deskId,
+                    desk.freeformTasksInZOrder.toDumpString(),
+                )
+            }
+        }
         boundsBeforeMaximizeByTaskId.remove(taskId)
         boundsBeforeFullImmersiveByTaskId.remove(taskId)
-        logD(
-            "Remaining freeform tasks: %s",
-            desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString(),
-        )
         // Remove task from unminimized task if it is minimized.
         unminimizeTask(displayId, taskId)
         // Mark task as not in immersive if it was immersive.
@@ -502,15 +700,18 @@
     }
 
     /**
-     * Removes the desktop for the given [displayId] and returns the active tasks on that desktop.
+     * Removes the active desk for the given [displayId] and returns the active tasks on that desk.
+     *
+     * TODO: b/389960283 - add explicit [deskId] argument.
      */
-    fun removeDesktop(displayId: Int): ArraySet<Int> {
-        if (!desktopTaskDataByDisplayId.contains(displayId)) {
-            logW("Could not find desktop to remove: displayId=%d", displayId)
+    fun removeDesk(displayId: Int): ArraySet<Int> {
+        val desk = desktopData.getActiveDesk(displayId)
+        if (desk == null) {
+            logW("Could not find desk to remove: displayId=%d", displayId)
             return ArraySet()
         }
-        val activeTasks = ArraySet(desktopTaskDataByDisplayId[displayId].activeTasks)
-        desktopTaskDataByDisplayId[displayId].clear()
+        val activeTasks = ArraySet(desk.activeTasks)
+        desktopData.remove(desk.deskId)
         return activeTasks
     }
 
@@ -564,19 +765,20 @@
     fun saveBoundsBeforeFullImmersive(taskId: Int, bounds: Rect) =
         boundsBeforeFullImmersiveByTaskId.set(taskId, Rect(bounds))
 
+    /** TODO: b/389960283 - consider updating only the changing desks. */
     private fun updatePersistentRepository(displayId: Int) {
-        // Create a deep copy of the data
-        desktopTaskDataByDisplayId[displayId]?.deepCopy()?.let { desktopTaskDataByDisplayIdCopy ->
-            mainCoroutineScope.launch {
+        val desks = desktopData.desksSequence(displayId).map { desk -> desk.deepCopy() }.toList()
+        mainCoroutineScope.launch {
+            desks.forEach { desk ->
                 try {
                     persistentRepository.addOrUpdateDesktop(
-                        // Use display id as desktop id for now since only once desktop per display
+                        // Use display id as desk id for now since only once desk per display
                         // is supported.
                         userId = userId,
-                        desktopId = displayId,
-                        visibleTasks = desktopTaskDataByDisplayIdCopy.visibleTasks,
-                        minimizedTasks = desktopTaskDataByDisplayIdCopy.minimizedTasks,
-                        freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder,
+                        desktopId = desk.deskId,
+                        visibleTasks = desk.visibleTasks,
+                        minimizedTasks = desk.minimizedTasks,
+                        freeformTasksInZOrder = desk.freeformTasksInZOrder,
                     )
                 } catch (exception: Exception) {
                     logE(
@@ -598,20 +800,27 @@
 
     private fun dumpDesktopTaskData(pw: PrintWriter, prefix: String) {
         val innerPrefix = "$prefix  "
-        desktopTaskDataByDisplayId.forEach { displayId, data ->
-            pw.println("${prefix}Display $displayId:")
-            pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
-            pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
-            pw.println(
-                "${innerPrefix}freeformTasksInZOrder=${data.freeformTasksInZOrder.toDumpString()}"
-            )
-            pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}")
-            pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}")
-            pw.println(
-                "${innerPrefix}topTransparentFullscreenTaskId=" +
-                    "${data.topTransparentFullscreenTaskId}"
-            )
-        }
+        desktopData
+            .desksSequence()
+            .groupBy { it.displayId }
+            .forEach { (displayId, desks) ->
+                pw.println("${prefix}Display #$displayId:")
+                desks.forEach { desk ->
+                    pw.println("${innerPrefix}Desk #${desk.deskId}:")
+                    pw.print("$innerPrefix  activeTasks=")
+                    pw.println(desk.activeTasks.toDumpString())
+                    pw.print("$innerPrefix  visibleTasks=")
+                    pw.println(desk.visibleTasks.toDumpString())
+                    pw.print("$innerPrefix  freeformTasksInZOrder=")
+                    pw.println(desk.freeformTasksInZOrder.toDumpString())
+                    pw.print("$innerPrefix  minimizedTasks=")
+                    pw.println(desk.minimizedTasks.toDumpString())
+                    pw.print("$innerPrefix  fullImmersiveTaskId=")
+                    pw.println(desk.fullImmersiveTaskId)
+                    pw.print("$innerPrefix  topTransparentFullscreenTaskId=")
+                    pw.println(desk.topTransparentFullscreenTaskId)
+                }
+            }
     }
 
     /** Listens to changes for active tasks in desktop mode. */
@@ -624,6 +833,227 @@
         fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
     }
 
+    /** An interface for the desktop hierarchy's data managed by this repository. */
+    private interface DesktopData {
+        /**
+         * Returns the existing desk or creates a new entry if needed.
+         *
+         * TODO: 389787966 - consider removing this as it cannot be assumed a desk can be created in
+         *   all devices / form-factors.
+         */
+        fun getOrCreateDesk(displayId: Int, deskId: Int): Desk
+
+        /** Returns the desk with the given id, or null if it does not exist. */
+        fun getDesk(deskId: Int): Desk?
+
+        /** Returns the active desk in this diplay, or null if none are active. */
+        fun getActiveDesk(displayId: Int): Desk?
+
+        /** Sets the given desk as the active desk in the given display. */
+        fun setActiveDesk(displayId: Int, deskId: Int)
+
+        /**
+         * Returns the default desk in the given display. Useful when the system wants to activate a
+         * desk but doesn't care about which one it activates (e.g. when putting a window into a
+         * desk using the App Handle). May return null if the display does not support desks.
+         *
+         * TODO: 389787966 - consider removing or renaming. In practice, this is needed for
+         *   soon-to-be deprecated IDesktopMode APIs, adb commands or entry-points into the only
+         *   desk (single-desk devices) or the most-recent desk (multi-desk devices).
+         */
+        fun getDefaultDesk(displayId: Int): Desk?
+
+        /** Returns all the active desks of all displays. */
+        fun getAllActiveDesks(): Set<Desk>
+
+        /** Returns the number of desks in the given display. */
+        fun getNumberOfDesks(displayId: Int): Int
+
+        /** Applies a function to all desks. */
+        fun forAllDesks(consumer: (Desk) -> Unit)
+
+        /** Applies a function to all desks. */
+        fun forAllDesks(consumer: (displayId: Int, Desk) -> Unit)
+
+        /** Applies a function to all desks under the given display. */
+        fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit)
+
+        /** Returns a sequence of all desks. */
+        fun desksSequence(): Sequence<Desk>
+
+        /** Returns a sequence of all desks under the given display. */
+        fun desksSequence(displayId: Int): Sequence<Desk>
+
+        /** Remove an existing desk if it exists. */
+        fun remove(deskId: Int)
+
+        /** Returns the id of the display where the given desk is located. */
+        fun getDisplayForDesk(deskId: Int): Int
+    }
+
+    /**
+     * A [DesktopData] implementation that only supports one desk per display.
+     *
+     * Internally, it reuses the displayId as that display's single desk's id.
+     */
+    private class SingleDesktopData : DesktopData {
+        private val deskByDisplayId =
+            object : SparseArray<Desk>() {
+                /** Gets [Desk] for existing [displayId] or creates a new one. */
+                fun getOrCreate(displayId: Int): Desk =
+                    this[displayId]
+                        ?: Desk(deskId = displayId, displayId = displayId).also {
+                            this[displayId] = it
+                        }
+            }
+
+        override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk {
+            check(displayId == deskId)
+            return deskByDisplayId.getOrCreate(displayId)
+        }
+
+        override fun getDesk(deskId: Int): Desk = getOrCreateDesk(deskId, deskId)
+
+        override fun getActiveDesk(displayId: Int): Desk {
+            // TODO: 389787966 - consider migrating to an "active" state instead of checking the
+            //   number of visible active tasks, PIP in desktop, and empty desktop logic. In
+            //   practice, existing single-desktop devices are ok with this function returning the
+            //   only desktop, even if it's not active.
+            return deskByDisplayId.getOrCreate(displayId)
+        }
+
+        override fun setActiveDesk(displayId: Int, deskId: Int) {
+            // No-op, in single-desk setups, which desktop is "active" is determined by the
+            // existence of visible desktop windows, among other factors.
+        }
+
+        override fun getDefaultDesk(displayId: Int): Desk = getOrCreateDesk(displayId, displayId)
+
+        override fun getAllActiveDesks(): Set<Desk> =
+            deskByDisplayId.valueIterator().asSequence().toSet()
+
+        override fun getNumberOfDesks(displayId: Int): Int = 1
+
+        override fun forAllDesks(consumer: (Desk) -> Unit) {
+            deskByDisplayId.forEach { _, desk -> consumer(desk) }
+        }
+
+        override fun forAllDesks(consumer: (Int, Desk) -> Unit) {
+            deskByDisplayId.forEach { displayId, desk -> consumer(displayId, desk) }
+        }
+
+        override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) {
+            consumer(getOrCreateDesk(displayId, displayId))
+        }
+
+        override fun desksSequence(): Sequence<Desk> = deskByDisplayId.valueIterator().asSequence()
+
+        override fun desksSequence(displayId: Int): Sequence<Desk> =
+            deskByDisplayId[displayId]?.let { sequenceOf(it) } ?: emptySequence()
+
+        override fun remove(deskId: Int) {
+            deskByDisplayId[deskId]?.clear()
+        }
+
+        override fun getDisplayForDesk(deskId: Int): Int = deskId
+    }
+
+    /** A [DesktopData] implementation that supports multiple desks. */
+    private class MultiDesktopData : DesktopData {
+        private val desktopDisplays = SparseArray<DesktopDisplay>()
+
+        override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk {
+            val display =
+                desktopDisplays[displayId]
+                    ?: DesktopDisplay(displayId).also { desktopDisplays[displayId] = it }
+            val desk =
+                display.orderedDesks.find { desk -> desk.deskId == deskId }
+                    ?: Desk(deskId = deskId, displayId = displayId).also {
+                        display.orderedDesks.add(it)
+                    }
+            return desk
+        }
+
+        override fun getDesk(deskId: Int): Desk? {
+            desktopDisplays.forEach { _, display ->
+                val desk = display.orderedDesks.find { desk -> desk.deskId == deskId }
+                if (desk != null) {
+                    return desk
+                }
+            }
+            return null
+        }
+
+        override fun getActiveDesk(displayId: Int): Desk? {
+            val display = desktopDisplays[displayId] ?: return null
+            if (display.activeDeskId == null) return null
+            return display.orderedDesks.find { it.deskId == display.activeDeskId }
+        }
+
+        override fun setActiveDesk(displayId: Int, deskId: Int) {
+            val display =
+                desktopDisplays[displayId] ?: error("Expected display#$displayId to exist")
+            val desk = display.orderedDesks.single { it.deskId == deskId }
+            display.activeDeskId = desk.deskId
+        }
+
+        override fun getDefaultDesk(displayId: Int): Desk? {
+            val display = desktopDisplays[displayId] ?: return null
+            return display.orderedDesks.firstOrNull()
+        }
+
+        override fun getAllActiveDesks(): Set<Desk> {
+            return desktopDisplays
+                .valueIterator()
+                .asSequence()
+                .filter { display -> display.activeDeskId != null }
+                .map { display ->
+                    display.orderedDesks.single { it.deskId == display.activeDeskId }
+                }
+                .toSet()
+        }
+
+        override fun getNumberOfDesks(displayId: Int): Int =
+            desktopDisplays[displayId]?.orderedDesks?.size ?: 0
+
+        override fun forAllDesks(consumer: (Desk) -> Unit) {
+            desktopDisplays.forEach { _, display -> display.orderedDesks.forEach { consumer(it) } }
+        }
+
+        override fun forAllDesks(consumer: (Int, Desk) -> Unit) {
+            desktopDisplays.forEach { _, display ->
+                display.orderedDesks.forEach { consumer(display.displayId, it) }
+            }
+        }
+
+        override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) {
+            desktopDisplays
+                .valueIterator()
+                .asSequence()
+                .filter { display -> display.displayId == displayId }
+                .flatMap { display -> display.orderedDesks.asSequence() }
+                .forEach { desk -> consumer(desk) }
+        }
+
+        override fun desksSequence(): Sequence<Desk> =
+            desktopDisplays.valueIterator().asSequence().flatMap { display ->
+                display.orderedDesks.asSequence()
+            }
+
+        override fun desksSequence(displayId: Int): Sequence<Desk> =
+            desktopDisplays[displayId]?.orderedDesks?.asSequence() ?: emptySequence()
+
+        override fun remove(deskId: Int) {
+            desktopDisplays.forEach { _, display ->
+                display.orderedDesks.removeIf { it.deskId == deskId }
+            }
+        }
+
+        override fun getDisplayForDesk(deskId: Int): Int =
+            getAllActiveDesks().find { it.deskId == deskId }?.displayId
+                ?: error("Display for desk=$deskId not found")
+    }
+
     private fun logD(msg: String, vararg arguments: Any?) {
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
     }
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 3d57038..172410d 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
@@ -35,6 +35,7 @@
 import android.graphics.Rect
 import android.graphics.Region
 import android.os.Binder
+import android.os.Bundle
 import android.os.Handler
 import android.os.IBinder
 import android.os.SystemProperties
@@ -86,6 +87,7 @@
 import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
 import com.android.wm.shell.compatui.isTransparentTask
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
@@ -465,7 +467,9 @@
         desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
             FREEFORM_ANIMATION_DURATION
         )
-        taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+        taskIdToMinimize?.let {
+            addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
+        }
         exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
         return true
     }
@@ -511,7 +515,9 @@
         desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
             FREEFORM_ANIMATION_DURATION
         )
-        taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+        taskIdToMinimize?.let {
+            addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
+        }
         exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
     }
 
@@ -572,7 +578,9 @@
             DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt()
         )
         transition?.let {
-            taskIdToMinimize?.let { taskId -> addPendingMinimizeTransition(it, taskId) }
+            taskIdToMinimize?.let { taskId ->
+                addPendingMinimizeTransition(it, taskId, MinimizeReason.TASK_LIMIT)
+            }
             exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
         }
     }
@@ -621,7 +629,7 @@
             ?.runOnTransitionStart
     }
 
-    fun minimizeTask(taskInfo: RunningTaskInfo) {
+    fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
         val wct = WindowContainerTransaction()
 
         val isMinimizingToPip = taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false
@@ -641,16 +649,16 @@
             freeformTaskTransitionStarter.startPipTransition(wct)
             taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true)
             taskRepository.setOnPipAbortedCallback { displayId, taskId ->
-                minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!)
+                minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!, minimizeReason)
                 taskRepository.setTaskInPip(displayId, taskId, enterPip = false)
             }
             return
         }
 
-        minimizeTaskInner(taskInfo)
+        minimizeTaskInner(taskInfo, minimizeReason)
     }
 
-    private fun minimizeTaskInner(taskInfo: RunningTaskInfo) {
+    private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
         val taskId = taskInfo.taskId
         val displayId = taskInfo.displayId
         val wct = WindowContainerTransaction()
@@ -670,6 +678,7 @@
                 transition = transition,
                 displayId = displayId,
                 taskId = taskId,
+                minimizeReason = minimizeReason,
             )
         }
         exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
@@ -825,7 +834,7 @@
                     minimizingTaskId = taskIdToMinimize,
                     exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask,
                 )
-            taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
+            taskIdToMinimize?.let { addPendingMinimizeTransition(t, it, MinimizeReason.TASK_LIMIT) }
             exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
             return t
         }
@@ -845,7 +854,7 @@
             )
         val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
         remoteTransitionHandler.setTransition(t)
-        taskIdToMinimize.let { addPendingMinimizeTransition(t, it) }
+        taskIdToMinimize.let { addPendingMinimizeTransition(t, it, MinimizeReason.TASK_LIMIT) }
         exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
         return t
     }
@@ -884,6 +893,36 @@
     }
 
     /**
+     * Start an intent through a launch transition for starting tasks whose transition does not get
+     * handled by [handleRequest]
+     */
+    fun startLaunchIntentTransition(intent: Intent, options: Bundle, displayId: Int) {
+        val wct = WindowContainerTransaction()
+        val displayLayout = displayController.getDisplayLayout(displayId) ?: return
+        val bounds = calculateDefaultDesktopTaskBounds(displayLayout)
+        if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) {
+            cascadeWindow(bounds, displayLayout, displayId)
+        }
+        val pendingIntent =
+            PendingIntent.getActivity(
+                context,
+                /* requestCode= */ 0,
+                intent,
+                PendingIntent.FLAG_IMMUTABLE,
+            )
+        val ops =
+            ActivityOptions.fromBundle(options).apply {
+                launchWindowingMode = WINDOWING_MODE_FREEFORM
+                pendingIntentBackgroundActivityStartMode =
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+                launchBounds = bounds
+            }
+
+        wct.sendPendingIntent(pendingIntent, intent, ops.toBundle())
+        startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null)
+    }
+
+    /**
      * Move [task] to display with [displayId].
      *
      * No-op if task is already on that display per [RunningTaskInfo.displayId].
@@ -1867,7 +1906,7 @@
         val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
         addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
         if (taskIdToMinimize != null) {
-            addPendingMinimizeTransition(transition, taskIdToMinimize)
+            addPendingMinimizeTransition(transition, taskIdToMinimize, MinimizeReason.TASK_LIMIT)
             return wct
         }
         if (!wct.isEmpty) {
@@ -1901,7 +1940,9 @@
                 // Desktop Mode is already showing and we're launching a new Task - we might need to
                 // minimize another Task.
                 val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
-                taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+                taskIdToMinimize?.let {
+                    addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
+                }
                 addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
                 desktopImmersiveController.exitImmersiveIfApplicable(
                     transition,
@@ -2149,13 +2190,18 @@
             .addAndGetMinimizeTaskChanges(displayId, wct, newTaskId, launchingNewIntent)
     }
 
-    private fun addPendingMinimizeTransition(transition: IBinder, taskIdToMinimize: Int) {
+    private fun addPendingMinimizeTransition(
+        transition: IBinder,
+        taskIdToMinimize: Int,
+        minimizeReason: MinimizeReason,
+    ) {
         val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
         desktopTasksLimiter.ifPresent {
             it.addPendingMinimizeChange(
                 transition = transition,
                 displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY,
                 taskId = taskIdToMinimize,
+                minimizeReason = minimizeReason,
             )
         }
     }
@@ -2185,7 +2231,7 @@
     fun removeDesktop(displayId: Int) {
         if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
 
-        val tasksToRemove = taskRepository.removeDesktop(displayId)
+        val tasksToRemove = taskRepository.removeDesk(displayId)
         val wct = WindowContainerTransaction()
         tasksToRemove.forEach {
             val task = shellTaskOrganizer.getRunningTaskInfo(it)
@@ -2904,6 +2950,12 @@
                 c.moveToNextDisplay(taskId)
             }
         }
+
+        override fun startLaunchIntentTransition(intent: Intent, options: Bundle, displayId: Int) {
+            executeRemoteCallWithTaskPermission(controller, "startLaunchIntentTransition") { c ->
+                c.startLaunchIntentTransition(intent, options, displayId)
+            }
+        }
     }
 
     private fun logV(msg: String, vararg arguments: Any?) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index e4a28e9..204b396 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -30,6 +30,7 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.shared.annotations.ShellMainThread
 import com.android.wm.shell.sysui.UserChangeListener
@@ -67,12 +68,21 @@
         logV("Starting limiter with a maximum of %d tasks", maxTasksLimit)
     }
 
-    private data class TaskDetails(
+    data class TaskDetails(
         val displayId: Int,
         val taskId: Int,
-        var transitionInfo: TransitionInfo?,
+        var transitionInfo: TransitionInfo? = null,
+        val minimizeReason: MinimizeReason? = null,
     )
 
+    /**
+     * Returns the task being minimized in the given transition if that transition is a pending or
+     * active minimize transition.
+     */
+    fun getMinimizingTask(transition: IBinder): TaskDetails? {
+        return minimizeTransitionObserver.getMinimizingTask(transition)
+    }
+
     // TODO(b/333018485): replace this observer when implementing the minimize-animation
     private inner class MinimizeTransitionObserver : TransitionObserver {
         private val pendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>()
@@ -82,6 +92,11 @@
             pendingTransitionTokensAndTasks[transition] = taskDetails
         }
 
+        fun getMinimizingTask(transition: IBinder): TaskDetails? {
+            return pendingTransitionTokensAndTasks[transition]
+                ?: activeTransitionTokensAndTasks[transition]
+        }
+
         override fun onTransitionReady(
             transition: IBinder,
             info: TransitionInfo,
@@ -89,6 +104,14 @@
             finishTransaction: SurfaceControl.Transaction,
         ) {
             val taskRepository = desktopUserRepositories.current
+            handleMinimizeTransition(taskRepository, transition, info)
+        }
+
+        private fun handleMinimizeTransition(
+            taskRepository: DesktopRepository,
+            transition: IBinder,
+            info: TransitionInfo,
+        ) {
             val taskToMinimize = pendingTransitionTokensAndTasks.remove(transition) ?: return
             if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return
             if (!isTaskReadyForMinimize(info, taskToMinimize)) {
@@ -241,10 +264,15 @@
      * Add a pending minimize transition change to update the list of minimized apps once the
      * transition goes through.
      */
-    fun addPendingMinimizeChange(transition: IBinder, displayId: Int, taskId: Int) {
+    fun addPendingMinimizeChange(
+        transition: IBinder,
+        displayId: Int,
+        taskId: Int,
+        minimizeReason: MinimizeReason,
+    ) {
         minimizeTransitionObserver.addPendingTransitionToken(
             transition,
-            TaskDetails(displayId, taskId, transitionInfo = null),
+            TaskDetails(displayId, taskId, transitionInfo = null, minimizeReason = minimizeReason),
         )
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index fa383cb..54f0312 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -17,6 +17,8 @@
 package com.android.wm.shell.desktopmode;
 
 import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Intent;
+import android.os.Bundle;
 import android.window.RemoteTransition;
 import com.android.wm.shell.desktopmode.IDesktopTaskListener;
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
@@ -61,4 +63,7 @@
 
     /** Move a task with given `taskId` to external display */
     void moveToExternalDisplay(int taskId);
+
+    /** Start a transition when launching an intent in desktop mode */
+    void startLaunchIntentTransition(in Intent intent, in Bundle options, in int displayId);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 9c3e815..912d383 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -1317,14 +1317,14 @@
         }
 
         @Override
-        public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
-                PictureInPictureParams pictureInPictureParams, int launcherRotation,
-                Rect keepClearArea) {
+        public Rect startSwipePipToHome(ActivityManager.RunningTaskInfo taskInfo,
+                int launcherRotation, Rect keepClearArea) {
             Rect[] result = new Rect[1];
             executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
                     (controller) -> {
-                        result[0] = controller.startSwipePipToHome(componentName, activityInfo,
-                                pictureInPictureParams, launcherRotation, keepClearArea);
+                        result[0] = controller.startSwipePipToHome(taskInfo.topActivity,
+                                taskInfo.topActivityInfo, taskInfo.pictureInPictureParams,
+                                launcherRotation, keepClearArea);
                     }, true /* blocking */);
             return result[0];
         }
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 63c1512..a033b824 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
@@ -50,6 +50,11 @@
 
     private final SurfaceControl mLeash;
     private final SurfaceControl.Transaction mStartTransaction;
+    private final SurfaceControl.Transaction mFinishTransaction;
+
+    private final int mDirection;
+    private final int mCornerRadius;
+    private final int mShadowRadius;
 
     private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
         @Override
@@ -59,6 +64,7 @@
                 mAnimationStartCallback.run();
             }
             if (mStartTransaction != null) {
+                onAlphaAnimationUpdate(getStartAlphaValue(), mStartTransaction);
                 mStartTransaction.apply();
             }
         }
@@ -66,6 +72,10 @@
         @Override
         public void onAnimationEnd(Animator animation) {
             super.onAnimationEnd(animation);
+            if (mFinishTransaction != null) {
+                onAlphaAnimationUpdate(getEndAlphaValue(), mFinishTransaction);
+                mFinishTransaction.apply();
+            }
             if (mAnimationEndCallback != null) {
                 mAnimationEndCallback.run();
             }
@@ -77,8 +87,9 @@
                 @Override
                 public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                     final float alpha = (Float) animation.getAnimatedValue();
-                    mSurfaceControlTransactionFactory.getTransaction()
-                            .setAlpha(mLeash, alpha).apply();
+                    final SurfaceControl.Transaction tx =
+                            mSurfaceControlTransactionFactory.getTransaction();
+                    onAlphaAnimationUpdate(alpha, tx);
                 }
             };
 
@@ -91,19 +102,21 @@
 
     public PipAlphaAnimator(Context context,
             SurfaceControl leash,
-            SurfaceControl.Transaction tx,
+            SurfaceControl.Transaction startTransaction,
+            SurfaceControl.Transaction finishTransaction,
             @Fade int direction) {
         mLeash = leash;
-        mStartTransaction = tx;
-        if (direction == FADE_IN) {
-            setFloatValues(0f, 1f);
-        } else { // direction == FADE_OUT
-            setFloatValues(1f, 0f);
-        }
+        mStartTransaction = startTransaction;
+        mFinishTransaction = finishTransaction;
+
+        mDirection = direction;
+        setFloatValues(getStartAlphaValue(), getEndAlphaValue());
         mSurfaceControlTransactionFactory =
                 new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
         final int enterAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipEnterAnimationDuration);
+        mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+        mShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
         setDuration(enterAnimationDuration);
         addListener(mAnimatorListener);
         addUpdateListener(mAnimatorUpdateListener);
@@ -117,6 +130,21 @@
         mAnimationEndCallback = runnable;
     }
 
+    private void onAlphaAnimationUpdate(float alpha, SurfaceControl.Transaction tx) {
+        tx.setAlpha(mLeash, alpha)
+                .setCornerRadius(mLeash, mCornerRadius)
+                .setShadowRadius(mLeash, mShadowRadius);
+        tx.apply();
+    }
+
+    private float getStartAlphaValue() {
+        return mDirection == FADE_IN ? 0f : 1f;
+    }
+
+    private float getEndAlphaValue() {
+        return mDirection == FADE_IN ? 1f : 0f;
+    }
+
     @VisibleForTesting
     void setSurfaceControlTransactionFactory(
             @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
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 562b260..b1984cc 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
@@ -40,6 +40,7 @@
 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;
@@ -358,10 +359,21 @@
     //
 
     private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo,
-            PictureInPictureParams pictureInPictureParams,
+            int displayId, PictureInPictureParams pictureInPictureParams,
             int launcherRotation, Rect hotseatKeepClearArea) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "getSwipePipToHomeBounds: %s", componentName);
+
+        // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
+        // display info that PiP is entering in.
+        if (Flags.enableConnectedDisplaysPip()) {
+            final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
+            if (displayLayout != null) {
+                mPipDisplayLayoutState.setDisplayId(displayId);
+                mPipDisplayLayoutState.setDisplayLayout(displayLayout);
+            }
+        }
+
         // Preemptively add the keep clear area for Hotseat, so that it is taken into account
         // when calculating the entry destination bounds of PiP window.
         mPipBoundsState.setNamedUnrestrictedKeepClearArea(
@@ -592,14 +604,14 @@
         }
 
         @Override
-        public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
-                PictureInPictureParams pictureInPictureParams, int launcherRotation,
-                Rect keepClearArea) {
+        public Rect startSwipePipToHome(ActivityManager.RunningTaskInfo taskInfo,
+                int launcherRotation, Rect keepClearArea) {
             Rect[] result = new Rect[1];
             executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
                     (controller) -> {
-                        result[0] = controller.getSwipePipToHomeBounds(componentName, activityInfo,
-                                pictureInPictureParams, launcherRotation, keepClearArea);
+                        result[0] = controller.getSwipePipToHomeBounds(taskInfo.topActivity,
+                                taskInfo.topActivityInfo, taskInfo.displayId,
+                                taskInfo.pictureInPictureParams, launcherRotation, keepClearArea);
                     }, true /* blocking */);
             return result[0];
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index ed532ca..21b0820 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -294,7 +294,8 @@
     interface PipAlphaAnimatorSupplier {
         PipAlphaAnimator get(@NonNull Context context,
                 SurfaceControl leash,
-                SurfaceControl.Transaction tx,
+                SurfaceControl.Transaction startTransaction,
+                SurfaceControl.Transaction finishTransaction,
                 @PipAlphaAnimator.Fade int direction);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 4902455..8cba076 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -59,6 +59,8 @@
 import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.ComponentUtils;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -112,6 +114,7 @@
     private final PipScheduler mPipScheduler;
     private final PipTransitionState mPipTransitionState;
     private final PipDisplayLayoutState mPipDisplayLayoutState;
+    private final DisplayController mDisplayController;
     private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
     private final Optional<DesktopWallpaperActivityTokenProvider>
             mDesktopWallpaperActivityTokenProviderOptional;
@@ -151,6 +154,7 @@
             PipTransitionState pipTransitionState,
             PipDisplayLayoutState pipDisplayLayoutState,
             PipUiStateChangeController pipUiStateChangeController,
+            DisplayController displayController,
             Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
             Optional<DesktopWallpaperActivityTokenProvider>
                     desktopWallpaperActivityTokenProviderOptional) {
@@ -164,6 +168,7 @@
         mPipTransitionState = pipTransitionState;
         mPipTransitionState.addPipTransitionStateChangedListener(this);
         mPipDisplayLayoutState = pipDisplayLayoutState;
+        mDisplayController = displayController;
         mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
         mDesktopWallpaperActivityTokenProviderOptional =
                 desktopWallpaperActivityTokenProviderOptional;
@@ -513,7 +518,7 @@
     private void startOverlayFadeoutAnimation(@NonNull SurfaceControl overlayLeash,
             @NonNull Runnable onAnimationEnd) {
         PipAlphaAnimator animator = new PipAlphaAnimator(mContext, overlayLeash,
-                null /* startTx */, PipAlphaAnimator.FADE_OUT);
+                null /* startTx */, null /* finishTx */, PipAlphaAnimator.FADE_OUT);
         animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
         animator.setAnimationEndCallback(onAnimationEnd);
         animator.start();
@@ -604,7 +609,7 @@
                 .setAlpha(pipLeash, 0f);
 
         PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipLeash, startTransaction,
-                PipAlphaAnimator.FADE_IN);
+                finishTransaction, PipAlphaAnimator.FADE_IN);
         // This should update the pip transition state accordingly after we stop playing.
         animator.setAnimationEndCallback(this::finishTransition);
         cacheAndStartTransitionAnimator(animator);
@@ -699,7 +704,7 @@
         finishTransaction.setAlpha(pipChange.getLeash(), 0f);
         if (mPendingRemoveWithFadeout) {
             PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(),
-                    startTransaction, PipAlphaAnimator.FADE_OUT);
+                    startTransaction, finishTransaction, PipAlphaAnimator.FADE_OUT);
             animator.setAnimationEndCallback(this::finishTransition);
             animator.start();
         } else {
@@ -824,6 +829,17 @@
         mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
                 pipParams, mPipBoundsAlgorithm);
 
+        // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
+        // display info that PiP is entering in.
+        if (Flags.enableConnectedDisplaysPip()) {
+            final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(
+                    pipTask.displayId);
+            if (displayLayout != null) {
+                mPipDisplayLayoutState.setDisplayId(pipTask.displayId);
+                mPipDisplayLayoutState.setDisplayLayout(displayLayout);
+            }
+        }
+
         // calculate the entry bounds and notify core to move task to pinned with final bounds
         final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         mPipBoundsState.setBounds(entryBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 0182588..2d4d458 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -43,7 +43,6 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.window.DesktopModeFlags;
 import android.window.WindowContainerToken;
@@ -83,6 +82,7 @@
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * Manages the recent task list from the system, caching it as necessary.
@@ -124,6 +124,10 @@
      * Cached list of the visible tasks, sorted from top most to bottom most.
      */
     private final List<RunningTaskInfo> mVisibleTasks = new ArrayList<>();
+    private final Map<Integer, TaskInfo> mVisibleTasksMap = new HashMap<>();
+
+    // Temporary vars used in `generateList()`
+    private final Map<Integer, TaskInfo> mTmpRemaining = new HashMap<>();
 
     /**
      * Creates {@link RecentTasksController}, returns {@code null} if the feature is not
@@ -348,8 +352,11 @@
     public void onVisibleTasksChanged(@NonNull List<? extends RunningTaskInfo> visibleTasks) {
         mVisibleTasks.clear();
         mVisibleTasks.addAll(visibleTasks);
+        mVisibleTasksMap.clear();
+        mVisibleTasksMap.putAll(mVisibleTasks.stream().collect(
+                Collectors.toMap(TaskInfo::getTaskId, task -> task)));
         // Notify with all the info and not just the running task info
-        notifyVisibleTasksChanged(visibleTasks);
+        notifyVisibleTasksChanged(mVisibleTasks);
     }
 
     @VisibleForTesting
@@ -458,7 +465,7 @@
         }
         try {
             // Compute the visible recent tasks in order, and move the task to the top
-            mListener.onVisibleTasksChanged(generateList(visibleTasks)
+            mListener.onVisibleTasksChanged(generateList(visibleTasks, "visibleTasksChanged")
                     .toArray(new GroupedTaskInfo[0]));
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed call onVisibleTasksChanged", e);
@@ -494,40 +501,87 @@
     @VisibleForTesting
     ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
         // Note: the returned task list is ordered from the most-recent to least-recent order
-        return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId));
+        return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId),
+                "getRecentTasks");
     }
 
     /**
-     * Generates a list of GroupedTaskInfos for the given list of tasks.
+     * Returns whether the given task should be excluded from the generated list.
      */
-    private <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks) {
-        // Make a mapping of task id -> task info
-        final SparseArray<TaskInfo> rawMapping = new SparseArray<>();
-        for (int i = 0; i < tasks.size(); i++) {
-            final TaskInfo taskInfo = tasks.get(i);
-            rawMapping.put(taskInfo.taskId, taskInfo);
+    private boolean excludeTaskFromGeneratedList(TaskInfo taskInfo) {
+        if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+            // We don't current send pinned tasks as a part of recent or running tasks
+            return true;
+        }
+        if (isWallpaperTask(taskInfo)) {
+            // Don't add the fullscreen wallpaper task as an entry in grouped tasks
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Generates a list of GroupedTaskInfos for the given raw list of tasks (either recents or
+     * running tasks).
+     *
+     * The general flow is:
+     *  - Collect the desktop tasks
+     *  - Collect the visible tasks (in order), including the desktop tasks if visible
+     *  - Construct the final list with the visible tasks, followed by the subsequent tasks
+     *      - if enableShellTopTaskTracking() is enabled, the visible tasks will be grouped into
+     *        a single mixed task
+     *      - if the desktop tasks are not visible, they will be appended to the end of the list
+     *
+     * TODO(346588978): Generate list in per-display order
+     *
+     * @param tasks The list of tasks ordered from most recent to least recent
+     */
+    @VisibleForTesting
+    <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks,
+            String reason) {
+        if (tasks.isEmpty()) {
+            return new ArrayList<>();
         }
 
-        ArrayList<TaskInfo> freeformTasks = new ArrayList<>();
-        Set<Integer> minimizedFreeformTasks = new HashSet<>();
+        if (enableShellTopTaskTracking()) {
+            ProtoLog.v(WM_SHELL_TASK_OBSERVER, "RecentTasksController.generateList(%s)", reason);
+        }
 
-        int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
+        // Make a mapping of task id -> task info for the remaining tasks to be processed, this
+        // mapping is used to keep track of split tasks that may exist later in the task list that
+        // should be ignored because they've already been grouped
+        mTmpRemaining.clear();
+        mTmpRemaining.putAll(tasks.stream().collect(
+                Collectors.toMap(TaskInfo::getTaskId, task -> task)));
 
-        ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>();
-        // Pull out the pairs as we iterate back in the list
+        // The final grouped tasks
+        ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>(tasks.size());
+        ArrayList<GroupedTaskInfo> visibleGroupedTasks = new ArrayList<>();
+
+        // Phase 1: Extract the desktop and visible fullscreen/split tasks. We make new collections
+        // here as the GroupedTaskInfo can store them without copying
+        ArrayList<TaskInfo> desktopTasks = new ArrayList<>();
+        Set<Integer> minimizedDesktopTasks = new HashSet<>();
+        boolean desktopTasksVisible = false;
         for (int i = 0; i < tasks.size(); i++) {
             final TaskInfo taskInfo = tasks.get(i);
-            if (!rawMapping.contains(taskInfo.taskId)) {
-                // If it's not in the mapping, then it was already paired with another task
+            final int taskId = taskInfo.taskId;
+
+            if (!mTmpRemaining.containsKey(taskInfo.taskId)) {
+                // Skip if we've already processed it
                 continue;
             }
+
+            if (excludeTaskFromGeneratedList(taskInfo)) {
+                // Skip and update the list if we are excluding this task
+                mTmpRemaining.remove(taskId);
+                continue;
+            }
+
+            // Desktop tasks
             if (DesktopModeStatus.canEnterDesktopMode(mContext) &&
-                mDesktopUserRepositories.isPresent()
-                    && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskInfo.taskId)) {
-                // Freeform tasks will be added as a separate entry
-                if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
-                    mostRecentFreeformTaskIndex = groupedTasks.size();
-                }
+                    mDesktopUserRepositories.isPresent()
+                    && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskId)) {
                 // If task has their app bounds set to null which happens after reboot, set the
                 // app bounds to persisted lastFullscreenBounds. Also set the position in parent
                 // to the top left of the bounds.
@@ -538,49 +592,132 @@
                     taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left,
                             taskInfo.lastNonFullscreenBounds.top);
                 }
-                freeformTasks.add(taskInfo);
-                if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskInfo.taskId)) {
-                    minimizedFreeformTasks.add(taskInfo.taskId);
+                desktopTasks.add(taskInfo);
+                if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskId)) {
+                    minimizedDesktopTasks.add(taskId);
                 }
+                desktopTasksVisible |= mVisibleTasksMap.containsKey(taskId);
+                mTmpRemaining.remove(taskId);
                 continue;
             }
 
-            final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
-            if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(pairedTaskId)) {
-                final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
-                rawMapping.remove(pairedTaskId);
-                groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
-                        mTaskSplitBoundsMap.get(pairedTaskId)));
+            if (enableShellTopTaskTracking()) {
+                // Visible tasks
+                if (mVisibleTasksMap.containsKey(taskId)) {
+                    // Split tasks
+                    if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining,
+                            visibleGroupedTasks)) {
+                        continue;
+                    }
+
+                    // Fullscreen tasks
+                    visibleGroupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
+                    mTmpRemaining.remove(taskId);
+                }
             } else {
-                if (isWallpaperTask(taskInfo)) {
-                    // Don't add the wallpaper task as an entry in grouped tasks
+                // Split tasks
+                if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) {
                     continue;
                 }
-                // TODO(346588978): Consolidate multiple visible fullscreen tasks into the same
-                //  grouped task
+
+                // Fullscreen tasks
                 groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
             }
         }
 
-        // Add a special entry for freeform tasks
-        if (!freeformTasks.isEmpty()) {
-            groupedTasks.add(mostRecentFreeformTaskIndex,
-                    GroupedTaskInfo.forFreeformTasks(
-                            freeformTasks,
-                            minimizedFreeformTasks));
-        }
-
         if (enableShellTopTaskTracking()) {
-            // We don't current send pinned tasks as a part of recent or running tasks, so remove
-            // them from the list here
-            groupedTasks.removeIf(
-                    gti -> gti.getTaskInfo1().getWindowingMode() == WINDOWING_MODE_PINNED);
+            // Phase 2: If there were desktop tasks and they are visible, add them to the visible
+            //          list as well (the actual order doesn't matter for Overview)
+            if (!desktopTasks.isEmpty() && desktopTasksVisible) {
+                visibleGroupedTasks.add(
+                        GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+            }
+
+            if (!visibleGroupedTasks.isEmpty()) {
+                // Phase 3: Combine the visible tasks into a single mixed grouped task, only if
+                //          there are > 1 tasks to group, and add them to the final list
+                if (visibleGroupedTasks.size() > 1) {
+                    groupedTasks.add(GroupedTaskInfo.forMixed(visibleGroupedTasks));
+                } else {
+                    groupedTasks.addAll(visibleGroupedTasks);
+                }
+            }
+            dumpGroupedTasks(groupedTasks, "Phase 3");
+
+            // Phase 4: For the remaining non-visible split and fullscreen tasks, add grouped tasks
+            //          in order to the final list
+            for (int i = 0; i < tasks.size(); i++) {
+                final TaskInfo taskInfo = tasks.get(i);
+                if (!mTmpRemaining.containsKey(taskInfo.taskId)) {
+                    // Skip if we've already processed it
+                    continue;
+                }
+
+                // Split tasks
+                if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) {
+                    continue;
+                }
+
+                // Fullscreen tasks
+                groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
+            }
+            dumpGroupedTasks(groupedTasks, "Phase 4");
+
+            // Phase 5: If there were desktop tasks and they are not visible (ie. weren't added
+            //          above), add them to the end of the final list (the actual order doesn't
+            //          matter for Overview)
+            if (!desktopTasks.isEmpty() && !desktopTasksVisible) {
+                groupedTasks.add(
+                        GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+            }
+            dumpGroupedTasks(groupedTasks, "Phase 5");
+        } else {
+            // Add the desktop tasks at the end of the list
+            if (!desktopTasks.isEmpty()) {
+                groupedTasks.add(
+                        GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+            }
         }
 
         return groupedTasks;
     }
 
     /**
+     * Only to be called from `generateList()`. If the given {@param taskInfo} has a paired task,
+     * then a split grouped task with the pair is added to {@param tasksOut}.
+     *
+     * @return whether a split task was extracted and added to the given list
+     */
+    private boolean extractAndAddSplitGroupedTask(@NonNull TaskInfo taskInfo,
+            @NonNull Map<Integer, TaskInfo> remainingTasks,
+            @NonNull ArrayList<GroupedTaskInfo> tasksOut) {
+        final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
+        if (pairedTaskId == INVALID_TASK_ID || !remainingTasks.containsKey(pairedTaskId)) {
+            return false;
+        }
+
+        // Add both this task and its pair to the list, and mark the paired task to be
+        // skipped when it is encountered in the list
+        final TaskInfo pairedTaskInfo = remainingTasks.get(pairedTaskId);
+        remainingTasks.remove(taskInfo.taskId);
+        remainingTasks.remove(pairedTaskId);
+        tasksOut.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
+                mTaskSplitBoundsMap.get(pairedTaskId)));
+        return true;
+    }
+
+    /** Dumps the set of tasks to protolog */
+    private void dumpGroupedTasks(List<GroupedTaskInfo> groupedTasks, String reason) {
+        if (!WM_SHELL_TASK_OBSERVER.isEnabled()) {
+            return;
+        }
+        ProtoLog.v(WM_SHELL_TASK_OBSERVER, "    Tasks (%s):", reason);
+        for (GroupedTaskInfo task : groupedTasks) {
+            ProtoLog.v(WM_SHELL_TASK_OBSERVER, "        %s", task);
+        }
+    }
+
+    /**
      * Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified.
      * NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index aeccd86..afc6fee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -228,7 +228,7 @@
                 break;
             }
         }
-        final int transitionType = Flags.enableShellTopTaskTracking()
+        final int transitionType = Flags.enableRecentsBookendTransition()
                 ? TRANSIT_START_RECENTS_TRANSITION
                 : TRANSIT_TO_FRONT;
         final IBinder transition = mTransitions.startTransition(transitionType,
@@ -920,7 +920,7 @@
                 return;
             }
 
-            if (Flags.enableShellTopTaskTracking()
+            if (Flags.enableRecentsBookendTransition()
                     && info.getType() == TRANSIT_END_RECENTS_TRANSITION
                     && mergeTarget == mTransition) {
                 // This is a pending finish, so merge the end transition to trigger completing the
@@ -1148,9 +1148,12 @@
                                 change, layer, info, t, mLeashMap);
                         appearedTargets[nextTargetIdx++] = target;
                         // reparent into the original `mInfo` since that's where we are animating.
-                        final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+                        final TransitionInfo.Root root = TransitionUtil.getRootFor(change, mInfo);
                         final boolean wasClosing = closingIdx >= 0;
-                        t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash());
+                        t.reparent(target.leash, root.getLeash());
+                        t.setPosition(target.leash,
+                                change.getStartAbsBounds().left - root.getOffset().x,
+                                change.getStartAbsBounds().top - root.getOffset().y);
                         t.setLayer(target.leash, layer);
                         if (wasClosing) {
                             // App was previously visible and is closing
@@ -1287,8 +1290,8 @@
                 return;
             }
 
-            if (mFinishCB == null
-                    || (Flags.enableShellTopTaskTracking() && mPendingFinishTransition != null)) {
+            if (mFinishCB == null || (Flags.enableRecentsBookendTransition()
+                    && mPendingFinishTransition != null)) {
                 Slog.e(TAG, "Duplicate call to finish");
                 if (runnerFinishCb != null) {
                     try {
@@ -1307,7 +1310,7 @@
                     && !mWillFinishToHome
                     && mPausingTasks != null
                     && mState == STATE_NORMAL;
-            if (!Flags.enableShellTopTaskTracking()) {
+            if (!Flags.enableRecentsBookendTransition()) {
                 // This is only necessary when the recents transition is finished using a finishWCT,
                 // otherwise a new transition will notify the relevant observers
                 if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
@@ -1440,7 +1443,7 @@
                             // We need to clear the WCT to send finishWCT=null for Recents.
                             wct.clear();
 
-                            if (Flags.enableShellTopTaskTracking()) {
+                            if (Flags.enableRecentsBookendTransition()) {
                                 // In this case, we've already started the PIP transition, so we can
                                 // clean up immediately
                                 mPendingRunnerFinishCb = runnerFinishCb;
@@ -1452,7 +1455,7 @@
                 }
             }
 
-            if (Flags.enableShellTopTaskTracking()) {
+            if (Flags.enableRecentsBookendTransition()) {
                 if (!wct.isEmpty()) {
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                             "[%d] RecentsController.finishInner: "
@@ -1571,7 +1574,7 @@
         /**
          * A temporary transition handler used with the pending finish transition, which runs the
          * cleanup/finish logic once the pending transition is merged/handled.
-         * This is only initialized if Flags.enableShellTopTaskTracking() is enabled.
+         * This is only initialized if Flags.enableRecentsBookendTransition() is enabled.
          */
         private class PendingFinishTransitionHandler implements Transitions.TransitionHandler {
             @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 93f2e4c..11cd403 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -193,16 +193,18 @@
     override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
 
     override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
-        if (enableShellTopTaskTracking()) {
-            if (pendingCloseTasks.isNotEmpty()) {
-                // Update the visible task list based on the pending close tasks
-                for (change in pendingCloseTasks) {
-                    visibleTasks.removeIf {
-                        it.taskId == change.taskId
-                    }
+        if (!enableShellTopTaskTracking()) {
+            return
+        }
+
+        if (pendingCloseTasks.isNotEmpty()) {
+            // Update the visible task list based on the pending close tasks
+            for (change in pendingCloseTasks) {
+                visibleTasks.removeIf {
+                    it.taskId == change.taskId
                 }
-                updateVisibleTasksList("transition-finished")
             }
+            updateVisibleTasksList("transition-finished")
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c9136b4..37c9351 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -138,6 +138,7 @@
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.common.split.OffscreenTouchZone;
 import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
@@ -556,6 +557,13 @@
             return true;
         }
 
+        if (PipUtils.isPip2ExperimentEnabled()
+                && request.getPipChange() != null && getSplitPosition(
+                request.getPipChange().getTaskInfo().taskId) != SPLIT_POSITION_UNDEFINED) {
+            // In PiP2, PiP-able task can also come in through the pip change request field.
+            return true;
+        }
+
         // If one of the splitting tasks support auto-pip, wm-core might reparent the task to TDA
         // and file a TRANSIT_PIP transition when finishing transitions.
         // @see com.android.server.wm.RootWindowContainer#moveActivityToPinnedRootTask
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 361d7663..0445add 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -74,13 +74,16 @@
     private final Rect mTmpRootRect = new Rect();
     private final int[] mTmpLocation = new int[2];
     private final Rect mBoundsOnScreen = new Rect();
+    private final TaskViewController mTaskViewController;
     private final TaskViewTaskController mTaskViewTaskController;
     private Region mObscuredTouchRegion;
     private Insets mCaptionInsets;
     private Handler mHandler;
 
-    public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
+    public TaskView(Context context, TaskViewController taskViewController,
+            TaskViewTaskController taskViewTaskController) {
         super(context, null, 0, 0, true /* disableBackgroundLayer */);
+        mTaskViewController = taskViewController;
         mTaskViewTaskController = taskViewTaskController;
         // TODO(b/266736992): Think about a better way to set the TaskViewBase on the
         //  TaskViewTaskController and vice-versa
@@ -100,7 +103,8 @@
      */
     public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
             @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
-        mTaskViewTaskController.startActivity(pendingIntent, fillInIntent, options, launchBounds);
+        mTaskViewController.startActivity(mTaskViewTaskController, pendingIntent, fillInIntent,
+                options, launchBounds);
     }
 
     /**
@@ -115,19 +119,20 @@
      */
     public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
             @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
-        mTaskViewTaskController.startShortcutActivity(shortcut, options, launchBounds);
+        mTaskViewController.startShortcutActivity(mTaskViewTaskController, shortcut, options,
+                launchBounds);
     }
 
     /**
      * Moves the current task in taskview out of the view and back to fullscreen.
      */
     public void moveToFullscreen() {
-        mTaskViewTaskController.moveToFullscreen();
+        mTaskViewController.moveTaskViewToFullscreen(mTaskViewTaskController);
     }
 
     @Override
     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
-        if (mTaskViewTaskController.isUsingShellTransitions()) {
+        if (mTaskViewController.isUsingShellTransitions()) {
             // No need for additional work as it is already taken care of during
             // prepareOpenAnimation().
             return;
@@ -222,14 +227,14 @@
      */
     public void onLocationChanged() {
         getBoundsOnScreen(mTmpRect);
-        mTaskViewTaskController.setWindowBounds(mTmpRect);
+        mTaskViewController.setTaskBounds(mTaskViewTaskController, mTmpRect);
     }
 
     /**
      * Call to remove the task from window manager. This task will not appear in recents.
      */
     public void removeTask() {
-        mTaskViewTaskController.removeTask();
+        mTaskViewController.removeTaskView(mTaskViewTaskController, null /* token */);
     }
 
     /**
@@ -254,7 +259,7 @@
     public void surfaceChanged(@androidx.annotation.NonNull SurfaceHolder holder, int format,
             int width, int height) {
         getBoundsOnScreen(mTmpRect);
-        mTaskViewTaskController.setWindowBounds(mTmpRect);
+        mTaskViewController.setTaskBounds(mTaskViewTaskController, mTmpRect);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewController.java
new file mode 100644
index 0000000..59becf7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewController.java
@@ -0,0 +1,133 @@
+/*
+ * 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.taskview;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+
+/**
+ * Interface which provides methods to control TaskView properties and state.
+ *
+ * <ul>
+ *     <li>To start an activity based task view, use {@link #startActivity}</li>
+ *
+ *     <li>To start an activity (represented by {@link ShortcutInfo}) based task view, use
+ *     {@link #startShortcutActivity}
+ *     </li>
+ *
+ *     <li>To start a root-task based task view, use {@link #startRootTask}.
+ *     This method is special as it doesn't create a root task and instead expects that the
+ *     launch root task is already created and started. This method just attaches the taskInfo to
+ *     the TaskView.
+ *     </li>
+ * </ul>
+ */
+public interface TaskViewController {
+    /** Registers a TaskView with this controller. */
+    void registerTaskView(@NonNull TaskViewTaskController tv);
+
+    /** Un-registers a TaskView from this controller. */
+    void unregisterTaskView(@NonNull TaskViewTaskController tv);
+
+    /**
+     * Launch an activity represented by {@link ShortcutInfo}.
+     * <p>The owner of this container must be allowed to access the shortcut information,
+     * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
+     *
+     * @param destination  the TaskView to start the shortcut into.
+     * @param shortcut     the shortcut used to launch the activity.
+     * @param options      options for the activity.
+     * @param launchBounds the bounds (window size and position) that the activity should be
+     *                     launched in, in pixels and in screen coordinates.
+     */
+    void startShortcutActivity(@NonNull TaskViewTaskController destination,
+            @NonNull ShortcutInfo shortcut,
+            @NonNull ActivityOptions options, @Nullable Rect launchBounds);
+
+    /**
+     * Launch a new activity into a TaskView
+     *
+     * @param destination   The TaskView to start the activity into.
+     * @param pendingIntent Intent used to launch an activity.
+     * @param fillInIntent  Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
+     * @param options       options for the activity.
+     * @param launchBounds  the bounds (window size and position) that the activity should be
+     *                      launched in, in pixels and in screen coordinates.
+     */
+    void startActivity(@NonNull TaskViewTaskController destination,
+            @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+            @NonNull ActivityOptions options, @Nullable Rect launchBounds);
+
+    /**
+     * Attaches the given root task {@code taskInfo} in the task view.
+     *
+     * <p> Since {@link ShellTaskOrganizer#createRootTask(int, int,
+     * ShellTaskOrganizer.TaskListener)} does not use the shell transitions flow, this method is
+     * used as an entry point for an already-created root-task in the task view.
+     *
+     * @param destination The TaskView to put the root-task into.
+     * @param taskInfo    the task info of the root task.
+     * @param leash       the {@link android.content.pm.ShortcutInfo.Surface} of the root task
+     * @param wct         The Window container work that should happen as part of this set up.
+     */
+    void startRootTask(@NonNull TaskViewTaskController destination,
+            ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+            @Nullable WindowContainerTransaction wct);
+
+    /**
+     * Closes a taskview and removes the task from window manager. This task will not appear in
+     * recents.
+     */
+    void removeTaskView(@NonNull TaskViewTaskController taskView,
+            @Nullable WindowContainerToken taskToken);
+
+    /**
+     * Moves the current task in TaskView out of the view and back to fullscreen.
+     */
+    void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView);
+
+    /**
+     * Starts a new transition to make the given {@code taskView} visible and optionally change
+     * the task order.
+     *
+     * @param taskView the task view which the visibility is being changed for
+     * @param visible  the new visibility of the task view
+     */
+    void setTaskViewVisible(TaskViewTaskController taskView, boolean visible);
+
+    /**
+     * Sets the task bounds to {@code boundsOnScreen}.
+     * Usually called when the taskview's position or size has changed.
+     *
+     * @param boundsOnScreen the on screen bounds of the surface view.
+     */
+    void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen);
+
+    /** Whether shell-transitions are currently enabled. */
+    boolean isUsingShellTransitions();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
index e4fcff0c..b2813bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
@@ -32,16 +32,16 @@
     private final ShellTaskOrganizer mTaskOrganizer;
     private final ShellExecutor mShellExecutor;
     private final SyncTransactionQueue mSyncQueue;
-    private final TaskViewTransitions mTaskViewTransitions;
+    private final TaskViewController mTaskViewController;
     private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
 
     public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
             ShellExecutor shellExecutor, SyncTransactionQueue syncQueue,
-            TaskViewTransitions taskViewTransitions) {
+            TaskViewController taskViewController) {
         mTaskOrganizer = taskOrganizer;
         mShellExecutor = shellExecutor;
         mSyncQueue = syncQueue;
-        mTaskViewTransitions = taskViewTransitions;
+        mTaskViewController = taskViewController;
     }
 
     public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
@@ -49,7 +49,7 @@
         mTaskOrganizer = taskOrganizer;
         mShellExecutor = shellExecutor;
         mSyncQueue = syncQueue;
-        mTaskViewTransitions = null;
+        mTaskViewController = null;
     }
 
     /**
@@ -61,8 +61,8 @@
 
     /** Creates an {@link TaskView} */
     public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
-        TaskView taskView = new TaskView(context, new TaskViewTaskController(context,
-                mTaskOrganizer, mTaskViewTransitions, mSyncQueue));
+        TaskView taskView = new TaskView(context, mTaskViewController, new TaskViewTaskController(
+                context, mTaskOrganizer, mTaskViewController, mSyncQueue));
         executor.execute(() -> {
             onCreate.accept(taskView);
         });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 5c7dd07..d19a7ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -16,32 +16,21 @@
 
 package com.android.wm.shell.taskview;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
 import android.gui.TrustedOverlay;
 import android.os.Binder;
 import android.util.CloseGuard;
-import android.util.Slog;
 import android.view.SurfaceControl;
 import android.view.WindowInsets;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.wm.shell.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
@@ -49,27 +38,10 @@
 import java.util.concurrent.Executor;
 
 /**
- * This class implements the core logic to show a task on the {@link TaskView}. All the {@link
+ * This class represents the visible aspect of a task in a {@link TaskView}. All the {@link
  * TaskView} to {@link TaskViewTaskController} interactions are done via direct method calls.
  *
  * The reverse communication is done via the {@link TaskViewBase} interface.
- *
- * <ul>
- *     <li>The entry point for an activity based task view is {@link
- *     TaskViewTaskController#startActivity(PendingIntent, Intent, ActivityOptions, Rect)}</li>
- *
- *     <li>The entry point for an activity (represented by {@link ShortcutInfo}) based task view
- *     is {@link TaskViewTaskController#startShortcutActivity(ShortcutInfo, ActivityOptions, Rect)}
- *     </li>
- *
- *     <li>The entry point for a root-task based task view is {@link
- *     TaskViewTaskController#startRootTask(ActivityManager.RunningTaskInfo, SurfaceControl,
- *     WindowContainerTransaction)}.
- *     This method is special as it doesn't create a root task and instead expects that the
- *     launch root task is already created and started. This method just attaches the taskInfo to
- *     the TaskView.
- *     </li>
- * </ul>
  */
 public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
 
@@ -82,7 +54,7 @@
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Executor mShellExecutor;
     private final SyncTransactionQueue mSyncQueue;
-    private final TaskViewTransitions mTaskViewTransitions;
+    private final TaskViewController mTaskViewController;
     private final Context mContext;
 
     /**
@@ -109,15 +81,15 @@
     private Rect mCaptionInsets;
 
     public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
-            TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
+            TaskViewController taskViewController, SyncTransactionQueue syncQueue) {
         mContext = context;
         mTaskOrganizer = organizer;
         mShellExecutor = organizer.getExecutor();
         mSyncQueue = syncQueue;
-        mTaskViewTransitions = taskViewTransitions;
+        mTaskViewController = taskViewController;
         mShellExecutor.execute(() -> {
-            if (mTaskViewTransitions != null) {
-                mTaskViewTransitions.addTaskView(this);
+            if (mTaskViewController != null) {
+                mTaskViewController.registerTaskView(this);
             }
         });
         mGuard.open("release");
@@ -140,6 +112,10 @@
         return mSurfaceControl;
     }
 
+    Context getContext() {
+        return mContext;
+    }
+
     /**
      * Sets the provided {@link TaskViewBase}, which is used to notify the client part about the
      * task related changes and getting the current bounds.
@@ -155,9 +131,12 @@
         return mIsInitialized;
     }
 
-    /** Until all users are converted, we may have mixed-use (eg. Car). */
-    public boolean isUsingShellTransitions() {
-        return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
+    WindowContainerToken getTaskToken() {
+        return mTaskToken;
+    }
+
+    void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) {
+        mTaskViewBase.setResizeBgColor(t, bgColor);
     }
 
     /**
@@ -173,122 +152,6 @@
     }
 
     /**
-     * Launch an activity represented by {@link ShortcutInfo}.
-     * <p>The owner of this container must be allowed to access the shortcut information,
-     * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
-     *
-     * @param shortcut     the shortcut used to launch the activity.
-     * @param options      options for the activity.
-     * @param launchBounds the bounds (window size and position) that the activity should be
-     *                     launched in, in pixels and in screen coordinates.
-     */
-    public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
-            @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
-        prepareActivityOptions(options, launchBounds);
-        LauncherApps service = mContext.getSystemService(LauncherApps.class);
-        if (isUsingShellTransitions()) {
-            mShellExecutor.execute(() -> {
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                wct.startShortcut(mContext.getPackageName(), shortcut, options.toBundle());
-                mTaskViewTransitions.startTaskView(wct, this, options.getLaunchCookie());
-            });
-            return;
-        }
-        try {
-            service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Launch a new activity.
-     *
-     * @param pendingIntent Intent used to launch an activity.
-     * @param fillInIntent  Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
-     * @param options       options for the activity.
-     * @param launchBounds  the bounds (window size and position) that the activity should be
-     *                      launched in, in pixels and in screen coordinates.
-     */
-    public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
-            @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
-        prepareActivityOptions(options, launchBounds);
-        if (isUsingShellTransitions()) {
-            mShellExecutor.execute(() -> {
-                WindowContainerTransaction wct = new WindowContainerTransaction();
-                wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
-                mTaskViewTransitions.startTaskView(wct, this, options.getLaunchCookie());
-            });
-            return;
-        }
-        try {
-            pendingIntent.send(mContext, 0 /* code */, fillInIntent,
-                    null /* onFinished */, null /* handler */, null /* requiredPermission */,
-                    options.toBundle());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-
-    /**
-     * Attaches the given root task {@code taskInfo} in the task view.
-     *
-     * <p> Since {@link ShellTaskOrganizer#createRootTask(int, int,
-     * ShellTaskOrganizer.TaskListener)} does not use the shell transitions flow, this method is
-     * used as an entry point for an already-created root-task in the task view.
-     *
-     * @param taskInfo the task info of the root task.
-     * @param leash    the {@link android.content.pm.ShortcutInfo.Surface} of the root task
-     * @param wct      The Window container work that should happen as part of this set up.
-     */
-    public void startRootTask(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
-            @Nullable WindowContainerTransaction wct) {
-        if (wct == null) {
-            wct = new WindowContainerTransaction();
-        }
-        // This method skips the regular flow where an activity task is launched as part of a new
-        // transition in taskview and then transition is intercepted using the launchcookie.
-        // The task here is already created and running, it just needs to be reparented, resized
-        // and tracked correctly inside taskview. Which is done by calling
-        // prepareOpenAnimationInternal() and then manually enqueuing the resulting window container
-        // transaction.
-        prepareOpenAnimationInternal(true /* newTask */, mTransaction /* startTransaction */,
-                null /* finishTransaction */, taskInfo, leash, wct);
-        mTransaction.apply();
-        mTaskViewTransitions.startInstantTransition(TRANSIT_CHANGE, wct);
-    }
-
-    /**
-     * Moves the current task in TaskView out of the view and back to fullscreen.
-     */
-    public void moveToFullscreen() {
-        if (mTaskToken == null) return;
-        mShellExecutor.execute(() -> {
-            WindowContainerTransaction wct = new WindowContainerTransaction();
-            wct.setWindowingMode(mTaskToken, WINDOWING_MODE_UNDEFINED);
-            wct.setAlwaysOnTop(mTaskToken, false);
-            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
-            mTaskViewTransitions.moveTaskViewToFullscreen(wct, this);
-            if (mListener != null) {
-                // Task is being "removed" from the clients perspective
-                mListener.onTaskRemovalStarted(mTaskInfo.taskId);
-            }
-        });
-    }
-
-    private void prepareActivityOptions(ActivityOptions options, Rect launchBounds) {
-        final Binder launchCookie = new Binder();
-        mShellExecutor.execute(() -> {
-            mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this);
-        });
-        options.setLaunchBounds(launchBounds);
-        options.setLaunchCookie(launchCookie);
-        options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        options.setRemoveWithTaskOrganizer(true);
-    }
-
-    /**
      * Release this container if it is initialized.
      */
     public void release() {
@@ -309,8 +172,8 @@
 
     private void performRelease() {
         mShellExecutor.execute(() -> {
-            if (mTaskViewTransitions != null) {
-                mTaskViewTransitions.removeTaskView(this);
+            if (mTaskViewController != null) {
+                mTaskViewController.unregisterTaskView(this);
             }
             mTaskOrganizer.removeListener(this);
             resetTaskInfo();
@@ -364,7 +227,7 @@
     @Override
     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl leash) {
-        if (isUsingShellTransitions()) {
+        if (mTaskViewController.isUsingShellTransitions()) {
             mPendingInfo = taskInfo;
             if (mTaskNotFound) {
                 // If we were already notified by shell transit that we don't have the
@@ -484,8 +347,8 @@
                 // Nothing to update, task is not yet available
                 return;
             }
-            if (isUsingShellTransitions()) {
-                mTaskViewTransitions.setTaskViewVisible(this, true /* visible */);
+            if (mTaskViewController.isUsingShellTransitions()) {
+                mTaskViewController.setTaskViewVisible(this, true /* visible */);
                 return;
             }
             // Reparent the task when this surface is created
@@ -497,56 +360,6 @@
     }
 
     /**
-     * Sets the window bounds to {@code boundsOnScreen}.
-     * Call when view position or size has changed. Can also be called before the animation when
-     * the final bounds are known.
-     * Do not call during the animation.
-     *
-     * @param boundsOnScreen the on screen bounds of the surface view.
-     */
-    public void setWindowBounds(Rect boundsOnScreen) {
-        if (mTaskToken == null) {
-            return;
-        }
-
-        if (isUsingShellTransitions()) {
-            mShellExecutor.execute(() -> {
-                // Sync Transactions can't operate simultaneously with shell transition collection.
-                mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
-            });
-            return;
-        }
-
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.setBounds(mTaskToken, boundsOnScreen);
-        mSyncQueue.queue(wct);
-    }
-
-    /**
-     * Call to remove the task from window manager. This task will not appear in recents.
-     */
-    void removeTask() {
-        if (mTaskToken == null) {
-            if (Flags.enableTaskViewControllerCleanup()) {
-                // We don't have a task yet. Only clean up the controller
-                mTaskViewTransitions.removeTaskView(this);
-            } else {
-                // Call to remove task before we have one, do nothing
-                Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
-            }
-            return;
-        }
-        // Cache it to avoid NPE and make sure to remove it from recents history.
-        // mTaskToken can be cleared in onTaskVanished() when the task is removed.
-        final WindowContainerToken taskToken = mTaskToken;
-        mShellExecutor.execute(() -> {
-            WindowContainerTransaction wct = new WindowContainerTransaction();
-            wct.removeTask(taskToken);
-            mTaskViewTransitions.closeTaskView(wct, this);
-        });
-    }
-
-    /**
      * Sets a region of the task to inset to allow for a caption bar.
      *
      * @param captionInsets the rect for the insets in screen coordinates.
@@ -583,8 +396,8 @@
                 return;
             }
 
-            if (isUsingShellTransitions()) {
-                mTaskViewTransitions.setTaskViewVisible(this, false /* visible */);
+            if (mTaskViewController.isUsingShellTransitions()) {
+                mTaskViewController.setTaskViewVisible(this, false /* visible */);
                 return;
             }
 
@@ -604,15 +417,16 @@
         }
     }
 
+    void notifyTaskRemovalStarted(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+        if (mListener == null) return;
+        final int taskId = taskInfo.taskId;
+        mListenerExecutor.execute(() -> mListener.onTaskRemovalStarted(taskId));
+    }
+
     /** Notifies listeners of a task being removed and stops intercepting back presses on it. */
     private void handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo) {
         if (taskInfo != null) {
-            if (mListener != null) {
-                final int taskId = taskInfo.taskId;
-                mListenerExecutor.execute(() -> {
-                    mListener.onTaskRemovalStarted(taskId);
-                });
-            }
+            notifyTaskRemovalStarted(taskInfo);
             mTaskViewBase.onTaskVanished(taskInfo);
         }
     }
@@ -651,9 +465,7 @@
             handleAndNotifyTaskRemoval(pendingInfo);
 
             // Make sure the task is removed
-            WindowContainerTransaction wct = new WindowContainerTransaction();
-            wct.removeTask(pendingInfo.token);
-            mTaskViewTransitions.closeTaskView(wct, this);
+            mTaskViewController.removeTaskView(this, pendingInfo.token);
         }
         resetTaskInfo();
     }
@@ -681,72 +493,23 @@
         resetTaskInfo();
     }
 
-    void prepareOpenAnimation(final boolean newTask,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
-            WindowContainerTransaction wct) {
-        prepareOpenAnimationInternal(newTask, startTransaction, finishTransaction, taskInfo, leash,
-                wct);
-    }
-
-    private TaskViewRepository.TaskViewState getState() {
-        return mTaskViewTransitions.getRepository().byTaskView(this);
-    }
-
-    private void prepareOpenAnimationInternal(final boolean newTask,
-            SurfaceControl.Transaction startTransaction,
-            SurfaceControl.Transaction finishTransaction,
-            ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
-            WindowContainerTransaction wct) {
+    /**
+     * Prepare this taskview to open {@param taskInfo}.
+     * @return The bounds of the task or {@code null} on failure (surface is destroyed)
+     */
+    Rect prepareOpen(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
         mPendingInfo = null;
         mTaskInfo = taskInfo;
         mTaskToken = mTaskInfo.token;
         mTaskLeash = leash;
-        if (mSurfaceCreated) {
-            // Surface is ready, so just reparent the task to this surface control
-            startTransaction.reparent(mTaskLeash, mSurfaceControl)
-                    .show(mTaskLeash);
-            // Also reparent on finishTransaction since the finishTransaction will reparent back
-            // to its "original" parent by default.
-            Rect boundsOnScreen = mTaskViewBase.getCurrentBoundsOnScreen();
-            if (finishTransaction != null) {
-                finishTransaction.reparent(mTaskLeash, mSurfaceControl)
-                        .setPosition(mTaskLeash, 0, 0)
-                        // TODO: maybe once b/280900002 is fixed this will be unnecessary
-                        .setWindowCrop(mTaskLeash, boundsOnScreen.width(), boundsOnScreen.height());
-            }
-            if (TaskViewTransitions.useRepo()) {
-                final TaskViewRepository.TaskViewState state = getState();
-                if (state != null) {
-                    state.mBounds.set(boundsOnScreen);
-                    state.mVisible = true;
-                }
-            } else {
-                mTaskViewTransitions.updateBoundsState(this, boundsOnScreen);
-                mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
-            }
-            wct.setBounds(mTaskToken, boundsOnScreen);
-            applyCaptionInsetsIfNeeded();
-        } else {
-            // The surface has already been destroyed before the task has appeared,
-            // so go ahead and hide the task entirely
-            wct.setHidden(mTaskToken, true /* hidden */);
-            mTaskViewTransitions.updateVisibilityState(this, false /* visible */);
-            // listener callback is below
+        if (!mSurfaceCreated) {
+            return null;
         }
-        if (newTask) {
-            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true /* intercept */);
-        }
+        return mTaskViewBase.getCurrentBoundsOnScreen();
+    }
 
-        if (mTaskInfo.taskDescription != null) {
-            int backgroundColor = mTaskInfo.taskDescription.getBackgroundColor();
-            mTaskViewBase.setResizeBgColor(startTransaction, backgroundColor);
-        }
-
-        // After the embedded task has appeared, set it to non-trimmable. This is important
-        // to prevent recents from trimming and removing the embedded task.
-        wct.setTaskTrimmableFromRecents(taskInfo.token, false /* isTrimmableFromRecents */);
+    /** Notify that the associated task has appeared. This will call appropriate listeners. */
+    void notifyAppeared(final boolean newTask) {
         mTaskViewBase.onTaskAppeared(mTaskInfo, mTaskLeash);
         if (mListener != null) {
             final int taskId = mTaskInfo.taskId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 0cbb7bd..6c90a90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.taskview;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -25,7 +27,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
+import android.os.Binder;
 import android.os.IBinder;
 import android.util.ArrayMap;
 import android.util.Slog;
@@ -33,11 +42,14 @@
 import android.view.WindowManager;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.VisibleForTesting;
 
 import com.android.wm.shell.Flags;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.shared.TransitionUtil;
 import com.android.wm.shell.transition.Transitions;
 
@@ -45,11 +57,12 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
 
 /**
  * Handles Shell Transitions that involve TaskView tasks.
  */
-public class TaskViewTransitions implements Transitions.TransitionHandler {
+public class TaskViewTransitions implements Transitions.TransitionHandler, TaskViewController {
     static final String TAG = "TaskViewTransitions";
 
     /**
@@ -65,6 +78,12 @@
     private final ArrayList<PendingTransition> mPending = new ArrayList<>();
     private final Transitions mTransitions;
     private final boolean[] mRegistered = new boolean[]{false};
+    private final ShellTaskOrganizer mTaskOrganizer;
+    private final Executor mShellExecutor;
+    private final SyncTransactionQueue mSyncQueue;
+
+    /** A temp transaction used for quick things. */
+    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
 
     /**
      * TaskView makes heavy use of startTransition. Only one shell-initiated transition can be
@@ -96,8 +115,12 @@
         }
     }
 
-    public TaskViewTransitions(Transitions transitions, TaskViewRepository repository) {
+    public TaskViewTransitions(Transitions transitions, TaskViewRepository repository,
+            ShellTaskOrganizer taskOrganizer, SyncTransactionQueue syncQueue) {
         mTransitions = transitions;
+        mTaskOrganizer = taskOrganizer;
+        mShellExecutor = taskOrganizer.getExecutor();
+        mSyncQueue = syncQueue;
         if (useRepo()) {
             mTaskViews = null;
         } else if (Flags.enableTaskViewControllerCleanup()) {
@@ -111,7 +134,8 @@
         // TODO(210041388): register here once we have an explicit ordering mechanism.
     }
 
-    static boolean useRepo() {
+    /** @return whether the shared taskview repository is being used. */
+    public static boolean useRepo() {
         return Flags.taskViewRepository() || Flags.enableBubbleAnything();
     }
 
@@ -119,7 +143,8 @@
         return mTaskViewRepo;
     }
 
-    void addTaskView(TaskViewTaskController tv) {
+    @Override
+    public void registerTaskView(TaskViewTaskController tv) {
         synchronized (mRegistered) {
             if (!mRegistered[0]) {
                 mRegistered[0] = true;
@@ -133,7 +158,8 @@
         }
     }
 
-    void removeTaskView(TaskViewTaskController tv) {
+    @Override
+    public void unregisterTaskView(TaskViewTaskController tv) {
         if (useRepo()) {
             mTaskViewRepo.remove(tv);
         } else {
@@ -142,27 +168,12 @@
         // Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
     }
 
-    boolean isEnabled() {
+    @Override
+    public boolean isUsingShellTransitions() {
         return mTransitions.isRegistered();
     }
 
     /**
-     * Looks through the pending transitions for a closing transaction that matches the provided
-     * `taskView`.
-     *
-     * @param taskView the pending transition should be for this.
-     */
-    private PendingTransition findPendingCloseTransition(TaskViewTaskController taskView) {
-        for (int i = mPending.size() - 1; i >= 0; --i) {
-            if (mPending.get(i).mTaskView != taskView) continue;
-            if (TransitionUtil.isClosingType(mPending.get(i).mType)) {
-                return mPending.get(i);
-            }
-        }
-        return null;
-    }
-
-    /**
      * Starts a transition outside of the handler associated with {@link TaskViewTransitions}.
      */
     public void startInstantTransition(@WindowManager.TransitionType int type,
@@ -264,6 +275,82 @@
         return findTaskView(taskInfo) != null;
     }
 
+    private void prepareActivityOptions(ActivityOptions options, Rect launchBounds,
+            @NonNull TaskViewTaskController destination) {
+        final Binder launchCookie = new Binder();
+        mShellExecutor.execute(() -> {
+            mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, destination);
+        });
+        options.setLaunchBounds(launchBounds);
+        options.setLaunchCookie(launchCookie);
+        options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        options.setRemoveWithTaskOrganizer(true);
+    }
+
+    @Override
+    public void startShortcutActivity(@NonNull TaskViewTaskController destination,
+            @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options,
+            @Nullable Rect launchBounds) {
+        prepareActivityOptions(options, launchBounds, destination);
+        final Context context = destination.getContext();
+        if (isUsingShellTransitions()) {
+            mShellExecutor.execute(() -> {
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                wct.startShortcut(context.getPackageName(), shortcut, options.toBundle());
+                startTaskView(wct, destination, options.getLaunchCookie());
+            });
+            return;
+        }
+        try {
+            LauncherApps service = context.getSystemService(LauncherApps.class);
+            service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void startActivity(@NonNull TaskViewTaskController destination,
+            @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+            @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
+        prepareActivityOptions(options, launchBounds, destination);
+        if (isUsingShellTransitions()) {
+            mShellExecutor.execute(() -> {
+                WindowContainerTransaction wct = new WindowContainerTransaction();
+                wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
+                startTaskView(wct, destination, options.getLaunchCookie());
+            });
+            return;
+        }
+        try {
+            pendingIntent.send(destination.getContext(), 0 /* code */, fillInIntent,
+                    null /* onFinished */, null /* handler */, null /* requiredPermission */,
+                    options.toBundle());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void startRootTask(@NonNull TaskViewTaskController destination,
+            ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+            @Nullable WindowContainerTransaction wct) {
+        if (wct == null) {
+            wct = new WindowContainerTransaction();
+        }
+        // This method skips the regular flow where an activity task is launched as part of a new
+        // transition in taskview and then transition is intercepted using the launchcookie.
+        // The task here is already created and running, it just needs to be reparented, resized
+        // and tracked correctly inside taskview. Which is done by calling
+        // prepareOpenAnimationInternal() and then manually enqueuing the resulting window container
+        // transaction.
+        prepareOpenAnimation(destination, true /* newTask */, mTransaction /* startTransaction */,
+                null /* finishTransaction */, taskInfo, leash, wct);
+        mTransaction.apply();
+        mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
+    }
+
+    @VisibleForTesting
     void startTaskView(@NonNull WindowContainerTransaction wct,
             @NonNull TaskViewTaskController taskView, @NonNull IBinder launchCookie) {
         updateVisibilityState(taskView, true /* visible */);
@@ -271,30 +358,53 @@
         startNextTransition();
     }
 
-    void closeTaskView(@NonNull WindowContainerTransaction wct,
-            @NonNull TaskViewTaskController taskView) {
+    @Override
+    public void removeTaskView(@NonNull TaskViewTaskController taskView,
+            @Nullable WindowContainerToken taskToken) {
+        final WindowContainerToken token = taskToken != null ? taskToken : taskView.getTaskToken();
+        if (token == null) {
+            // We don't have a task yet, so just clean up records
+            if (!Flags.enableTaskViewControllerCleanup()) {
+                // Call to remove task before we have one, do nothing
+                Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
+                return;
+            }
+            unregisterTaskView(taskView);
+            return;
+        }
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.removeTask(token);
         updateVisibilityState(taskView, false /* visible */);
-        mPending.add(new PendingTransition(TRANSIT_CLOSE, wct, taskView, null /* cookie */));
-        startNextTransition();
+        mShellExecutor.execute(() -> {
+            mPending.add(new PendingTransition(TRANSIT_CLOSE, wct, taskView, null /* cookie */));
+            startNextTransition();
+        });
     }
 
-    void moveTaskViewToFullscreen(@NonNull WindowContainerTransaction wct,
-            @NonNull TaskViewTaskController taskView) {
-        mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */));
-        startNextTransition();
+    @Override
+    public void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView) {
+        final WindowContainerToken taskToken = taskView.getTaskToken();
+        if (taskToken == null) return;
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setWindowingMode(taskToken, WINDOWING_MODE_UNDEFINED);
+        wct.setAlwaysOnTop(taskToken, false);
+        mShellExecutor.execute(() -> {
+            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskToken, false);
+            mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */));
+            startNextTransition();
+            taskView.notifyTaskRemovalStarted(taskView.getTaskInfo());
+        });
     }
 
-    /** Starts a new transition to make the given {@code taskView} visible. */
+    @Override
     public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) {
         setTaskViewVisible(taskView, visible, false /* reorder */);
     }
 
     /**
-     * Starts a new transition to make the given {@code taskView} visible and optionally change
-     * the task order.
+     * Starts a new transition to make the given {@code taskView} visible and optionally
+     * reordering it.
      *
-     * @param taskView the task view which the visibility is being changed for
-     * @param visible  the new visibility of the task view
      * @param reorder  whether to reorder the task or not. If this is {@code true}, the task will
      *                 be reordered as per the given {@code visible}. For {@code visible = true},
      *                 task will be reordered to top. For {@code visible = false}, task will be
@@ -359,7 +469,26 @@
         state.mVisible = visible;
     }
 
-    void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
+    @Override
+    public void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
+        if (taskView.getTaskToken() == null) {
+            return;
+        }
+
+        if (isUsingShellTransitions()) {
+            mShellExecutor.execute(() -> {
+                // Sync Transactions can't operate simultaneously with shell transition collection.
+                setTaskBoundsInTransition(taskView, boundsOnScreen);
+            });
+            return;
+        }
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setBounds(taskView.getTaskToken(), boundsOnScreen);
+        mSyncQueue.queue(wct);
+    }
+
+    private void setTaskBoundsInTransition(TaskViewTaskController taskView, Rect boundsOnScreen) {
         final TaskViewRepository.TaskViewState state = useRepo()
                 ? mTaskViewRepo.byTaskView(taskView)
                 : mTaskViews.get(taskView);
@@ -476,7 +605,7 @@
                     }
                 }
                 if (wct == null) wct = new WindowContainerTransaction();
-                tv.prepareOpenAnimation(taskIsNew, startTransaction, finishTransaction,
+                prepareOpenAnimation(tv, taskIsNew, startTransaction, finishTransaction,
                         chg.getTaskInfo(), chg.getLeash(), wct);
                 changesHandled++;
             } else if (chg.getMode() == TRANSIT_CHANGE) {
@@ -510,4 +639,60 @@
         startNextTransition();
         return true;
     }
+
+    @VisibleForTesting
+    void prepareOpenAnimation(TaskViewTaskController taskView,
+            final boolean newTask,
+            SurfaceControl.Transaction startTransaction,
+            SurfaceControl.Transaction finishTransaction,
+            ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+            WindowContainerTransaction wct) {
+        final Rect boundsOnScreen = taskView.prepareOpen(taskInfo, leash);
+        if (boundsOnScreen != null) {
+            final SurfaceControl tvSurface = taskView.getSurfaceControl();
+            // Surface is ready, so just reparent the task to this surface control
+            startTransaction.reparent(leash, tvSurface)
+                    .show(leash);
+            // Also reparent on finishTransaction since the finishTransaction will reparent back
+            // to its "original" parent by default.
+            if (finishTransaction != null) {
+                finishTransaction.reparent(leash, tvSurface)
+                        .setPosition(leash, 0, 0)
+                        // TODO: maybe once b/280900002 is fixed this will be unnecessary
+                        .setWindowCrop(leash, boundsOnScreen.width(), boundsOnScreen.height());
+            }
+            if (useRepo()) {
+                final TaskViewRepository.TaskViewState state = mTaskViewRepo.byTaskView(taskView);
+                if (state != null) {
+                    state.mBounds.set(boundsOnScreen);
+                    state.mVisible = true;
+                }
+            } else {
+                updateBoundsState(taskView, boundsOnScreen);
+                updateVisibilityState(taskView, true /* visible */);
+            }
+            wct.setBounds(taskInfo.token, boundsOnScreen);
+            taskView.applyCaptionInsetsIfNeeded();
+        } else {
+            // The surface has already been destroyed before the task has appeared,
+            // so go ahead and hide the task entirely
+            wct.setHidden(taskInfo.token, true /* hidden */);
+            updateVisibilityState(taskView, false /* visible */);
+            // listener callback is below
+        }
+        if (newTask) {
+            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, true /* intercept */);
+        }
+
+        if (taskInfo.taskDescription != null) {
+            int backgroundColor = taskInfo.taskDescription.getBackgroundColor();
+            taskView.setResizeBgColor(startTransaction, backgroundColor);
+        }
+
+        // After the embedded task has appeared, set it to non-trimmable. This is important
+        // to prevent recents from trimming and removing the embedded task.
+        wct.setTaskTrimmableFromRecents(taskInfo.token, false /* isTrimmableFromRecents */);
+
+        taskView.notifyAppeared(newTask);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
index e61929f..2133275 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -17,13 +17,14 @@
 package com.android.wm.shell.transition;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 
-import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -36,6 +37,7 @@
 import android.window.TransitionInfo;
 
 import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -54,6 +56,7 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
                 + "entering PIP while Split-Screen is foreground.");
         TransitionInfo.Change pipChange = null;
+        TransitionInfo.Change pipActivityChange = null;
         TransitionInfo.Change wallpaper = null;
         final TransitionInfo everythingElse =
                 subCopy(info, TRANSIT_TO_BACK, true /* changes */);
@@ -68,6 +71,13 @@
                 pipChange = change;
                 // going backwards, so remove-by-index is fine.
                 everythingElse.getChanges().remove(i);
+            } else if (change.getTaskInfo() == null && change.getParent() != null
+                    && pipChange != null && change.getParent().equals(pipChange.getContainer())) {
+                // Cache the PiP activity if it's a target and cached pip task change is its parent;
+                // note that we are bottom-to-top, so if such activity has a task
+                // that is also a target, then it must have been cached already as pipChange.
+                pipActivityChange = change;
+                everythingElse.getChanges().remove(i);
             } else if (isHomeOpening(change)) {
                 homeIsOpening = true;
             } else if (isWallpaper(change)) {
@@ -138,9 +148,19 @@
                 }
             }
 
-            pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
-            pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
-                    finishCB);
+            if (PipUtils.isPip2ExperimentEnabled()) {
+                TransitionInfo pipInfo = subCopy(info, TRANSIT_PIP, false /* withChanges */);
+                pipInfo.getChanges().add(pipChange);
+                if (pipActivityChange != null) {
+                    pipInfo.getChanges().add(pipActivityChange);
+                }
+                pipHandler.startAnimation(mixed.mTransition, pipInfo, startTransaction,
+                        finishTransaction, finishCB);
+            } else {
+                pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
+                pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
+                        finishCB);
+            }
             // make a new finishTransaction because pip's startEnterAnimation "consumes" it so
             // we need a separate one to send over to launcher.
             SurfaceControl.Transaction otherFinishT = new SurfaceControl.Transaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 792f5ca..7aa0037 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -284,6 +284,10 @@
     }
 
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+        if (mDisplayController.getDisplay(taskInfo.displayId) == null) {
+            // If DisplayController doesn't have it tracked, it could be a private/managed display.
+            return false;
+        }
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             return true;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
index 0d75e65..7948ead 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
@@ -110,9 +110,6 @@
             SurfaceControl taskSurface,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        if (!shouldShowWindowDecor(taskInfo)) {
-            return false;
-        }
         createWindowDecoration(taskInfo, taskSurface, startT, finishT);
         return true;
     }
@@ -125,12 +122,9 @@
             return;
         }
 
-        if (!shouldShowWindowDecor(taskInfo)) {
-            destroyWindowDecoration(taskInfo);
-            return;
-        }
-
-        decoration.relayout(taskInfo, decoration.mHasGlobalFocus, decoration.mExclusionRegion);
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        decoration.relayout(taskInfo, t, t,
+                /* isCaptionVisible= */ shouldShowWindowDecor(taskInfo));
     }
 
     @Override
@@ -221,7 +215,8 @@
                         mWindowDecorViewHostSupplier,
                         new ButtonClickListener(taskInfo));
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
-        windowDecoration.relayout(taskInfo, startT, finishT);
+        windowDecoration.relayout(taskInfo, startT, finishT,
+                /* isCaptionVisible= */ shouldShowWindowDecor(taskInfo));
     }
 
     private class ButtonClickListener implements View.OnClickListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
index 1ca82d2..3943784 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
@@ -20,14 +20,17 @@
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.content.Context;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.View;
+import android.view.WindowInsets;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -44,6 +47,7 @@
     private WindowDecorLinearLayout mRootView;
     private @ShellBackgroundThread final ShellExecutor mBgExecutor;
     private final View.OnClickListener mClickListener;
+    private final RelayoutResult<WindowDecorLinearLayout> mResult = new RelayoutResult<>();
 
     CarWindowDecoration(
             Context context,
@@ -71,26 +75,32 @@
     @SuppressLint("MissingPermission")
     void relayout(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
+        relayout(taskInfo, startT, finishT, /* isCaptionVisible= */ true);
+    }
+
+    @SuppressLint("MissingPermission")
+    void relayout(ActivityManager.RunningTaskInfo taskInfo,
+            SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
+            boolean isCaptionVisible) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
         RelayoutParams relayoutParams = new RelayoutParams();
-        RelayoutResult<WindowDecorLinearLayout> outResult = new RelayoutResult<>();
 
         updateRelayoutParams(relayoutParams, taskInfo,
-                mDisplayController.getInsetsState(taskInfo.displayId));
+                mDisplayController.getInsetsState(taskInfo.displayId), isCaptionVisible);
 
-        relayout(relayoutParams, startT, finishT, wct, mRootView, outResult);
+        relayout(relayoutParams, startT, finishT, wct, mRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
         mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct));
 
-        if (outResult.mRootView == null) {
+        if (mResult.mRootView == null) {
             // This means something blocks the window decor from showing, e.g. the task is hidden.
             // Nothing is set up in this case including the decoration surface.
             return;
         }
-        if (mRootView != outResult.mRootView) {
-            mRootView = outResult.mRootView;
-            setupRootView(outResult.mRootView, mClickListener);
+        if (mRootView != mResult.mRootView) {
+            mRootView = mResult.mRootView;
+            setupRootView(mResult.mRootView, mClickListener);
         }
     }
 
@@ -108,18 +118,31 @@
     private void updateRelayoutParams(
             RelayoutParams relayoutParams,
             ActivityManager.RunningTaskInfo taskInfo,
-            InsetsState displayInsetsState) {
+            @Nullable InsetsState displayInsetsState,
+            boolean isCaptionVisible) {
         relayoutParams.reset();
         relayoutParams.mRunningTaskInfo = taskInfo;
         // todo(b/382071404): update to car specific UI
         relayoutParams.mLayoutResId = R.layout.caption_window_decor;
         relayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
-        relayoutParams.mIsCaptionVisible = mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded;
-        relayoutParams.mCaptionTopPadding = 0;
+        relayoutParams.mIsCaptionVisible =
+                isCaptionVisible && mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded;
+        if (displayInsetsState != null) {
+            relayoutParams.mCaptionTopPadding = getTopPadding(
+                    taskInfo.getConfiguration().windowConfiguration.getBounds(),
+                    displayInsetsState);
+        }
         relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
         relayoutParams.mApplyStartTransactionOnDraw = true;
     }
 
+    private static int getTopPadding(Rect taskBounds, @NonNull InsetsState insetsState) {
+        Insets systemDecor = insetsState.calculateInsets(taskBounds,
+                WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(),
+                false /* ignoreVisibility */);
+        return systemDecor.top;
+    }
+
     /**
      * Sets up listeners when a new root view is created.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
index ff52a45..575aac38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
@@ -74,8 +74,7 @@
     override fun addToContainer(menuView: ManageWindowsView) {
         val menuPosition = Point(x, y)
         val flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
-                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
-                WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
         val desktopRepository = desktopUserRepositories.getProfile(callerTaskInfo.userId)
         menuViewContainer = if (Flags.enableFullyImmersiveInDesktop()
             && desktopRepository.isTaskInFullImmersiveState(callerTaskInfo.taskId)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index d6d393f..51b0291 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -34,6 +34,7 @@
 import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
 import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
 import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod;
+import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason;
 import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
@@ -980,7 +981,8 @@
                             ToggleTaskSizeInteraction.AmbiguousSource.HEADER_BUTTON, mMotionEvent);
                 }
             } else if (id == R.id.minimize_window) {
-                mDesktopTasksController.minimizeTask(decoration.mTaskInfo);
+                mDesktopTasksController.minimizeTask(
+                        decoration.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON);
             }
         }
 
@@ -1641,6 +1643,10 @@
     }
 
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+        if (mDisplayController.getDisplay(taskInfo.displayId) == null) {
+            // If DisplayController doesn't have it tracked, it could be a private/managed display.
+            return false;
+        }
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
         if (mSplitScreenController != null
                 && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 7d1471f..b531079 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -17,7 +17,6 @@
 package com.android.wm.shell.windowdecor;
 
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
@@ -122,7 +121,7 @@
                     mDecorationSurface,
                     mClientToken,
                     null /* hostInputToken */,
-                    FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
+                    FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
                     INPUT_FEATURE_SPY,
                     TYPE_APPLICATION,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 9d73950..e5c989e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -245,8 +245,7 @@
                     width = menuWidth,
                     height = menuHeight,
                     flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
-                            WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
-                            WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+                            WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
                     view = handleMenuView.rootView,
                     forciblyShownTypes = if (forceShowSystemBars) { systemBars() } else { 0 },
                     ignoreCutouts = Flags.showAppHandleLargeScreens()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index cc54d25..1ce0366 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -178,8 +178,7 @@
                 menuHeight,
                 WindowManager.LayoutParams.TYPE_APPLICATION,
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+                        or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
                 PixelFormat.TRANSPARENT
         )
         lp.title = "Maximize Menu for Task=" + taskInfo.taskId
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index fa7183ad..3fcb093 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -22,7 +22,6 @@
 import static android.view.WindowInsets.Type.mandatorySystemGestures;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
@@ -333,7 +332,7 @@
                         outResult.mCaptionWidth,
                         outResult.mCaptionHeight,
                         TYPE_APPLICATION,
-                        FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
+                        FLAG_NOT_FOCUSABLE,
                         PixelFormat.TRANSPARENT);
         lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
         lp.setTrustedOverlay();
@@ -750,7 +749,7 @@
                         width,
                         height,
                         TYPE_APPLICATION,
-                        FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH,
+                        FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH,
                         PixelFormat.TRANSPARENT);
         lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
         lp.setTrustedOverlay();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
index da41e1b..4a09614 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
@@ -24,7 +24,6 @@
 import android.view.View
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
 import android.view.WindowManager.LayoutParams.TYPE_APPLICATION
 import android.widget.FrameLayout
 import androidx.tracing.Trace
@@ -72,7 +71,7 @@
                     0 /* width*/,
                     0 /* height */,
                     TYPE_APPLICATION,
-                    FLAG_NOT_FOCUSABLE or FLAG_SPLIT_TOUCH,
+                    FLAG_NOT_FOCUSABLE,
                     PixelFormat.TRANSPARENT,
                 )
                 .apply {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index 5832822..fbbf1a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -34,7 +34,6 @@
 import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
 import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
 import android.view.WindowManager.LayoutParams.FLAG_SLIPPERY
-import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
 import android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
@@ -240,7 +239,6 @@
                 FLAG_NOT_FOCUSABLE or
                     FLAG_NOT_TOUCH_MODAL or
                     FLAG_WATCH_OUTSIDE_TOUCH or
-                    FLAG_SPLIT_TOUCH or
                     FLAG_SLIPPERY,
                 PixelFormat.TRANSLUCENT,
             )
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index ce640b5..ffcc344 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -445,6 +445,15 @@
         assertThat(update.updatedBubble.showFlyout()).isFalse();
     }
 
+    @Test
+    public void getOrCreateBubble_withIntent_usesCorrectUser() {
+        Intent intent = new Intent();
+        intent.setPackage(mContext.getPackageName());
+        Bubble b = mBubbleData.getOrCreateBubble(intent, UserHandle.of(/* userId= */ 10));
+
+        assertThat(b.getUser().getIdentifier()).isEqualTo(10);
+    }
+
     //
     // Overflow
     //
@@ -1441,12 +1450,6 @@
         assertWithMessage("selectedBubble").that(update.selectedBubble).isEqualTo(bubble);
     }
 
-    private void assertSelectionCleared() {
-        BubbleData.Update update = mUpdateCaptor.getValue();
-        assertWithMessage("selectionChanged").that(update.selectionChanged).isTrue();
-        assertWithMessage("selectedBubble").that(update.selectedBubble).isNull();
-    }
-
     private void assertExpandedChangedTo(boolean expected) {
         BubbleData.Update update = mUpdateCaptor.getValue();
         assertWithMessage("expandedChanged").that(update.expandedChanged).isTrue();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index eeb83df..417b43a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -46,6 +46,7 @@
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewRepository
 import com.android.wm.shell.taskview.TaskViewTransitions
 import com.android.wm.shell.transition.Transitions
 import com.google.common.truth.Truth.assertThat
@@ -137,6 +138,7 @@
                 mainExecutor,
                 mock<Handler>(),
                 bgExecutor,
+                mock<TaskViewRepository>(),
                 mock<TaskViewTransitions>(),
                 mock<Transitions>(),
                 mock<SyncTransactionQueue>(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
index 04f9ada..03aad1c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
@@ -21,6 +21,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.app.WindowConfiguration.WindowingMode
+import android.os.Handler
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.SurfaceControl
@@ -52,6 +53,7 @@
 
     @Mock lateinit var testExecutor: ShellExecutor
     @Mock lateinit var closingTaskLeash: SurfaceControl
+    @Mock lateinit var mockHandler: Handler
 
     private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
 
@@ -65,6 +67,7 @@
                 testExecutor,
                 testExecutor,
                 transactionSupplier,
+                mockHandler,
             )
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index 413e7bc..016e040 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -46,6 +46,7 @@
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
 import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -294,7 +295,7 @@
         testExecutor.flushAll()
 
         assertThat(result).isTrue()
-        verify(desktopTasksController).minimizeTask(task)
+        verify(desktopTasksController).minimizeTask(task, MinimizeReason.KEY_GESTURE)
     }
 
     private fun setUpFreeformTask(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 4317143..a9ebcef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -42,6 +42,7 @@
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
@@ -62,6 +63,7 @@
 import com.android.wm.shell.transition.TransitionInfoBuilder
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TRANSIT_MINIMIZE
+import java.util.Optional
 import kotlin.test.assertFalse
 import kotlin.test.assertTrue
 import org.junit.Before
@@ -69,6 +71,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.`when`
 import org.mockito.kotlin.any
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
@@ -102,6 +105,8 @@
     private val mockShellInit = mock<ShellInit>()
     private val transitions = mock<Transitions>()
     private val context = mock<Context>()
+    private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+    private val desktopTasksLimiter = mock<DesktopTasksLimiter>()
 
     private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
     private lateinit var shellInit: ShellInit
@@ -119,6 +124,8 @@
                 mockShellInit,
                 transitions,
                 desktopModeEventLogger,
+                Optional.of(desktopTasksLimiter),
+                shellTaskOrganizer,
             )
         val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
         verify(mockShellInit)
@@ -755,6 +762,39 @@
         verify(desktopModeEventLogger, never()).logSessionExit(any())
     }
 
+    @Test
+    fun onTransitionReady_taskIsBeingMinimized_logsTaskMinimized() {
+        transitionObserver.isSessionActive = true
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 1))
+        val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)
+        transitionObserver.addTaskInfosToCachedMap(taskInfo2)
+        val transitionInfo =
+            TransitionInfoBuilder(TRANSIT_TO_BACK, 0)
+                .addChange(createChange(TRANSIT_TO_BACK, taskInfo2))
+                .build()
+        `when`(desktopTasksLimiter.getMinimizingTask(any()))
+            .thenReturn(
+                DesktopTasksLimiter.TaskDetails(
+                    taskInfo2.displayId,
+                    taskInfo2.taskId,
+                    minimizeReason = MinimizeReason.TASK_LIMIT,
+                )
+            )
+
+        callOnTransitionReady(transitionInfo)
+
+        verify(desktopModeEventLogger, times(1))
+            .logTaskRemoved(
+                eq(
+                    DEFAULT_TASK_UPDATE.copy(
+                        instanceId = 2,
+                        visibleTaskCount = 1,
+                        minimizeReason = MinimizeReason.TASK_LIMIT,
+                    )
+                )
+            )
+    }
+
     /** Simulate calling the onTransitionReady() method */
     private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
         val transition = mock<IBinder>()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 6003a21..8d73f3f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -1046,14 +1046,14 @@
     }
 
     @Test
-    fun removeDesktop_multipleTasks_removesAll() {
+    fun removeDesk_multipleTasks_removesAll() {
         // The front-most task will be the one added last through `addTask`.
         repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true)
         repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
         repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
         repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
 
-        val tasksBeforeRemoval = repo.removeDesktop(displayId = DEFAULT_DISPLAY)
+        val tasksBeforeRemoval = repo.removeDesk(displayId = DEFAULT_DISPLAY)
 
         assertThat(tasksBeforeRemoval).containsExactly(1, 2, 3).inOrder()
         assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty()
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 4b749d1..6c4f043 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
@@ -102,6 +102,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopTasksController.DesktopModeEntryExitTransitionListener
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
@@ -1146,6 +1147,21 @@
     }
 
     @Test
+    fun launchIntent_taskInDesktopMode_transitionStarted() {
+        setUpLandscapeDisplay()
+        val freeformTask = setUpFreeformTask()
+
+        controller.startLaunchIntentTransition(
+            freeformTask.baseIntent,
+            Bundle.EMPTY,
+            DEFAULT_DISPLAY,
+        )
+
+        val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+        assertThat(wct.hierarchyOps).hasSize(1)
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
         setUpLandscapeDisplay()
@@ -2147,7 +2163,7 @@
         whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
             .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
 
-        controller.minimizeTask(pipTask)
+        controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON)
         verifyExitDesktopWCTNotExecuted()
 
         taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = false)
@@ -2167,7 +2183,7 @@
         whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
             .thenReturn(transition)
 
-        controller.minimizeTask(task)
+        controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
         val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2183,7 +2199,7 @@
         whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
             .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
 
-        controller.minimizeTask(task)
+        controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
         verify(freeformTaskTransitionStarter).startPipTransition(any())
         verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any())
@@ -2195,7 +2211,7 @@
         whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
             .thenReturn(Binder())
 
-        controller.minimizeTask(task)
+        controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any())
         verify(freeformTaskTransitionStarter, never()).startPipTransition(any())
@@ -2208,7 +2224,7 @@
         whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
             .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
 
-        controller.minimizeTask(task)
+        controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
         val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         verify(freeformTaskTransitionStarter).startPipTransition(captor.capture())
@@ -2224,7 +2240,7 @@
         whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
             .thenReturn(transition)
 
-        controller.minimizeTask(task)
+        controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
         val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2240,7 +2256,7 @@
             .thenReturn(transition)
 
         // The only active task is being minimized.
-        controller.minimizeTask(task)
+        controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
         val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2257,7 +2273,7 @@
         taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
 
         // The only active task is already minimized.
-        controller.minimizeTask(task)
+        controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
         val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2274,7 +2290,7 @@
         whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
             .thenReturn(transition)
 
-        controller.minimizeTask(task1)
+        controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
 
         val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2294,7 +2310,7 @@
         taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
 
         // task1 is the only visible task as task2 is minimized.
-        controller.minimizeTask(task1)
+        controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
         // Adds remove wallpaper operation
         val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
@@ -2309,7 +2325,7 @@
         whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
             .thenReturn(transition)
 
-        controller.minimizeTask(task)
+        controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
         verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any())
     }
@@ -2326,7 +2342,7 @@
                 ExitResult.Exit(exitingTask = task.taskId, runOnTransitionStart = runOnTransit)
             )
 
-        controller.minimizeTask(task)
+        controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
 
         assertThat(runOnTransit.invocations).isEqualTo(1)
         assertThat(runOnTransit.lastInvoked).isEqualTo(transition)
@@ -3270,7 +3286,7 @@
         whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
             .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
 
-        controller.minimizeTask(pipTask)
+        controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON)
         verifyExitDesktopWCTNotExecuted()
 
         freeformTask.isFocused = true
@@ -5204,7 +5220,7 @@
         val arg: ArgumentCaptor<WindowContainerTransaction> =
             ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         verify(desktopMixedTransitionHandler)
-            .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull(), anyOrNull())
+            .startLaunchTransition(eq(type), capture(arg), anyOrNull(), anyOrNull(), anyOrNull())
         return arg.value
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index c8214b3..acfe1e9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -20,6 +20,7 @@
 import android.graphics.Rect
 import android.os.Binder
 import android.os.Handler
+import android.os.IBinder
 import android.os.UserManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
@@ -43,6 +44,7 @@
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
@@ -180,7 +182,7 @@
         val task = setUpFreeformTask()
         markTaskHidden(task)
 
-        desktopTasksLimiter.addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId)
+        addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId)
 
         assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse()
     }
@@ -208,11 +210,7 @@
         val taskTransition = Binder()
         val task = setUpFreeformTask()
         markTaskHidden(task)
-        desktopTasksLimiter.addPendingMinimizeChange(
-            pendingTransition,
-            displayId = DEFAULT_DISPLAY,
-            taskId = task.taskId,
-        )
+        addPendingMinimizeChange(pendingTransition, taskId = task.taskId)
 
         desktopTasksLimiter
             .getTransitionObserver()
@@ -231,11 +229,7 @@
         val transition = Binder()
         val task = setUpFreeformTask()
         markTaskVisible(task)
-        desktopTasksLimiter.addPendingMinimizeChange(
-            transition,
-            displayId = DEFAULT_DISPLAY,
-            taskId = task.taskId,
-        )
+        addPendingMinimizeChange(transition, taskId = task.taskId)
 
         desktopTasksLimiter
             .getTransitionObserver()
@@ -254,11 +248,7 @@
         val transition = Binder()
         val task = setUpFreeformTask()
         markTaskHidden(task)
-        desktopTasksLimiter.addPendingMinimizeChange(
-            transition,
-            displayId = DEFAULT_DISPLAY,
-            taskId = task.taskId,
-        )
+        addPendingMinimizeChange(transition, taskId = task.taskId)
 
         desktopTasksLimiter
             .getTransitionObserver()
@@ -276,11 +266,7 @@
     fun onTransitionReady_pendingTransition_changeTaskToBack_taskIsMinimized() {
         val transition = Binder()
         val task = setUpFreeformTask()
-        desktopTasksLimiter.addPendingMinimizeChange(
-            transition,
-            displayId = DEFAULT_DISPLAY,
-            taskId = task.taskId,
-        )
+        addPendingMinimizeChange(transition, taskId = task.taskId)
 
         desktopTasksLimiter
             .getTransitionObserver()
@@ -299,11 +285,7 @@
         val bounds = Rect(0, 0, 200, 200)
         val transition = Binder()
         val task = setUpFreeformTask()
-        desktopTasksLimiter.addPendingMinimizeChange(
-            transition,
-            displayId = DEFAULT_DISPLAY,
-            taskId = task.taskId,
-        )
+        addPendingMinimizeChange(transition, taskId = task.taskId)
 
         val change =
             TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply {
@@ -330,11 +312,7 @@
         val mergedTransition = Binder()
         val newTransition = Binder()
         val task = setUpFreeformTask()
-        desktopTasksLimiter.addPendingMinimizeChange(
-            mergedTransition,
-            displayId = DEFAULT_DISPLAY,
-            taskId = task.taskId,
-        )
+        addPendingMinimizeChange(mergedTransition, taskId = task.taskId)
         desktopTasksLimiter
             .getTransitionObserver()
             .onTransitionMerged(mergedTransition, newTransition)
@@ -541,11 +519,7 @@
         (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
         val transition = Binder()
         val task = setUpFreeformTask()
-        desktopTasksLimiter.addPendingMinimizeChange(
-            transition,
-            displayId = DEFAULT_DISPLAY,
-            taskId = task.taskId,
-        )
+        addPendingMinimizeChange(transition, taskId = task.taskId)
 
         desktopTasksLimiter
             .getTransitionObserver()
@@ -573,11 +547,7 @@
         (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() }
         val transition = Binder()
         val task = setUpFreeformTask()
-        desktopTasksLimiter.addPendingMinimizeChange(
-            transition,
-            displayId = DEFAULT_DISPLAY,
-            taskId = task.taskId,
-        )
+        addPendingMinimizeChange(transition, taskId = task.taskId)
 
         desktopTasksLimiter
             .getTransitionObserver()
@@ -606,11 +576,7 @@
         val mergedTransition = Binder()
         val newTransition = Binder()
         val task = setUpFreeformTask()
-        desktopTasksLimiter.addPendingMinimizeChange(
-            mergedTransition,
-            displayId = DEFAULT_DISPLAY,
-            taskId = task.taskId,
-        )
+        addPendingMinimizeChange(mergedTransition, taskId = task.taskId)
 
         desktopTasksLimiter
             .getTransitionObserver()
@@ -633,6 +599,60 @@
         verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
     }
 
+    @Test
+    fun getMinimizingTask_noPendingTransition_returnsNull() {
+        val transition = Binder()
+
+        assertThat(desktopTasksLimiter.getMinimizingTask(transition)).isNull()
+    }
+
+    @Test
+    fun getMinimizingTask_pendingTaskTransition_returnsTask() {
+        val transition = Binder()
+        val task = setUpFreeformTask()
+        addPendingMinimizeChange(
+            transition,
+            taskId = task.taskId,
+            minimizeReason = MinimizeReason.TASK_LIMIT,
+        )
+
+        assertThat(desktopTasksLimiter.getMinimizingTask(transition))
+            .isEqualTo(
+                createTaskDetails(taskId = task.taskId, minimizeReason = MinimizeReason.TASK_LIMIT)
+            )
+    }
+
+    @Test
+    fun getMinimizingTask_activeTaskTransition_returnsTask() {
+        val transition = Binder()
+        val task = setUpFreeformTask()
+        addPendingMinimizeChange(
+            transition,
+            taskId = task.taskId,
+            minimizeReason = MinimizeReason.TASK_LIMIT,
+        )
+        val transitionInfo =
+            TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build()
+
+        desktopTasksLimiter
+            .getTransitionObserver()
+            .onTransitionReady(
+                transition,
+                transitionInfo,
+                /* startTransaction= */ StubTransaction(),
+                /* finishTransaction= */ StubTransaction(),
+            )
+
+        assertThat(desktopTasksLimiter.getMinimizingTask(transition))
+            .isEqualTo(
+                createTaskDetails(
+                    taskId = task.taskId,
+                    transitionInfo = transitionInfo,
+                    minimizeReason = MinimizeReason.TASK_LIMIT,
+                )
+            )
+    }
+
     private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
         val task = createFreeformTask(displayId)
         `when`(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
@@ -640,6 +660,20 @@
         return task
     }
 
+    private fun createTaskDetails(
+        displayId: Int = DEFAULT_DISPLAY,
+        taskId: Int,
+        transitionInfo: TransitionInfo? = null,
+        minimizeReason: MinimizeReason? = null,
+    ) = DesktopTasksLimiter.TaskDetails(displayId, taskId, transitionInfo, minimizeReason)
+
+    fun addPendingMinimizeChange(
+        transition: IBinder,
+        displayId: Int = DEFAULT_DISPLAY,
+        taskId: Int,
+        minimizeReason: MinimizeReason = MinimizeReason.TASK_LIMIT,
+    ) = desktopTasksLimiter.addPendingMinimizeChange(transition, displayId, taskId, minimizeReason)
+
     private fun markTaskVisible(task: RunningTaskInfo) {
         desktopTaskRepo.updateTask(task.displayId, task.taskId, isVisible = true)
     }
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 9cc18ff..607e6a4 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
@@ -19,6 +19,8 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -33,6 +35,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
 
 import org.junit.Before;
@@ -48,6 +51,8 @@
 @TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner.class)
 public class PipAlphaAnimatorTest {
+    private static final float TEST_CORNER_RADIUS = 1f;
+    private static final float TEST_SHADOW_RADIUS = 2f;
 
     @Mock private Context mMockContext;
 
@@ -55,7 +60,9 @@
 
     @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
 
-    @Mock private SurfaceControl.Transaction mMockTransaction;
+    @Mock private SurfaceControl.Transaction mMockAnimateTransaction;
+    @Mock private SurfaceControl.Transaction mMockStartTransaction;
+    @Mock private SurfaceControl.Transaction mMockFinishTransaction;
 
     @Mock private Runnable mMockStartCallback;
 
@@ -69,9 +76,15 @@
         MockitoAnnotations.initMocks(this);
         when(mMockContext.getResources()).thenReturn(mMockResources);
         when(mMockResources.getInteger(anyInt())).thenReturn(0);
-        when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
-        when(mMockTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
-                .thenReturn(mMockTransaction);
+        when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction);
+        when(mMockResources.getDimensionPixelSize(R.dimen.pip_corner_radius))
+                .thenReturn((int) TEST_CORNER_RADIUS);
+        when(mMockResources.getDimensionPixelSize(R.dimen.pip_shadow_radius))
+                .thenReturn((int) TEST_SHADOW_RADIUS);
+
+        prepareTransaction(mMockAnimateTransaction);
+        prepareTransaction(mMockStartTransaction);
+        prepareTransaction(mMockFinishTransaction);
 
         mTestLeash = new SurfaceControl.Builder()
                 .setContainerLayer()
@@ -82,8 +95,8 @@
 
     @Test
     public void setAnimationStartCallback_fadeInAnimator_callbackStartCallback() {
-        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
-                PipAlphaAnimator.FADE_IN);
+        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+                mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
 
         mPipAlphaAnimator.setAnimationStartCallback(mMockStartCallback);
         mPipAlphaAnimator.setAnimationEndCallback(mMockEndCallback);
@@ -98,8 +111,8 @@
 
     @Test
     public void setAnimationEndCallback_fadeInAnimator_callbackStartAndEndCallback() {
-        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
-                PipAlphaAnimator.FADE_IN);
+        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+                mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
 
         mPipAlphaAnimator.setAnimationStartCallback(mMockStartCallback);
         mPipAlphaAnimator.setAnimationEndCallback(mMockEndCallback);
@@ -109,36 +122,98 @@
         });
 
         verify(mMockStartCallback).run();
-        verify(mMockStartCallback).run();
+        verify(mMockEndCallback).run();
+    }
+
+    @Test
+    public void onAnimationStart_setCornerAndShadowRadii() {
+        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+                mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
+        mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipAlphaAnimator.start();
+            mPipAlphaAnimator.pause();
+        });
+
+        verify(mMockStartTransaction, atLeastOnce())
+                .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+        verify(mMockStartTransaction, atLeastOnce())
+                .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+    }
+
+    @Test
+    public void onAnimationUpdate_setCornerAndShadowRadii() {
+        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+                mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
+        mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipAlphaAnimator.start();
+            mPipAlphaAnimator.pause();
+        });
+
+        verify(mMockAnimateTransaction, atLeastOnce())
+                .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+        verify(mMockAnimateTransaction, atLeastOnce())
+                .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+    }
+
+    @Test
+    public void onAnimationEnd_setCornerAndShadowRadii() {
+        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+                mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
+        mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipAlphaAnimator.start();
+            mPipAlphaAnimator.end();
+        });
+
+        verify(mMockFinishTransaction, atLeastOnce())
+                .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+        verify(mMockFinishTransaction, atLeastOnce())
+                .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
     }
 
     @Test
     public void onAnimationEnd_fadeInAnimator_leashVisibleAtEnd() {
-        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
-                PipAlphaAnimator.FADE_IN);
+        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+                mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
         mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mPipAlphaAnimator.start();
-            clearInvocations(mMockTransaction);
+            clearInvocations(mMockAnimateTransaction);
             mPipAlphaAnimator.end();
         });
 
-        verify(mMockTransaction).setAlpha(mTestLeash, 1.0f);
+        verify(mMockAnimateTransaction).setAlpha(mTestLeash, 1.0f);
     }
 
     @Test
     public void onAnimationEnd_fadeOutAnimator_leashInvisibleAtEnd() {
-        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockTransaction,
-                PipAlphaAnimator.FADE_OUT);
+        mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+                mMockFinishTransaction, PipAlphaAnimator.FADE_OUT);
         mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mPipAlphaAnimator.start();
-            clearInvocations(mMockTransaction);
+            clearInvocations(mMockAnimateTransaction);
             mPipAlphaAnimator.end();
         });
 
-        verify(mMockTransaction).setAlpha(mTestLeash, 0f);
+        verify(mMockAnimateTransaction).setAlpha(mTestLeash, 0f);
+    }
+
+
+    // set up transaction chaining
+    private void prepareTransaction(SurfaceControl.Transaction tx) {
+        when(tx.setAlpha(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(tx);
+        when(tx.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(tx);
+        when(tx.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(tx);
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index aef44a4..bd857c7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -111,7 +111,7 @@
                 mRootTaskDisplayAreaOrganizer);
         mPipScheduler.setPipTransitionController(mMockPipTransitionController);
         mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
-        mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, tx, direction) ->
+        mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, startTx, finishTx, direction) ->
                 mMockAlphaAnimator);
 
         SurfaceControl testLeash = new SurfaceControl.Builder()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 28f4ea0..065fa21 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
@@ -28,7 +29,6 @@
 import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -48,9 +48,10 @@
 import static org.mockito.Mockito.when;
 
 import static java.lang.Integer.MAX_VALUE;
+import static java.util.stream.Collectors.joining;
 
-import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
 import android.app.KeyguardManager;
 import android.content.ComponentName;
@@ -247,10 +248,10 @@
 
         ArrayList<GroupedTaskInfo> recentTasks =
                 mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
-        assertGroupedTasksListEquals(recentTasks,
-                t1.taskId, -1,
-                t2.taskId, -1,
-                t3.taskId, -1);
+        assertGroupedTasksListEquals(recentTasks, List.of(
+                List.of(t1.taskId),
+                List.of(t2.taskId),
+                List.of(t3.taskId)));
     }
 
     @Test
@@ -262,7 +263,9 @@
 
         ArrayList<GroupedTaskInfo> recentTasks =
                 mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
-        assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t3.taskId, -1);
+        assertGroupedTasksListEquals(recentTasks, List.of(
+                List.of(t1.taskId),
+                List.of(t3.taskId)));
     }
 
     @Test
@@ -286,11 +289,11 @@
 
         ArrayList<GroupedTaskInfo> recentTasks =
                 mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
-        assertGroupedTasksListEquals(recentTasks,
-                t1.taskId, -1,
-                t2.taskId, t4.taskId,
-                t3.taskId, t5.taskId,
-                t6.taskId, -1);
+        assertGroupedTasksListEquals(recentTasks, List.of(
+                List.of(t1.taskId),
+                List.of(t2.taskId, t4.taskId),
+                List.of(t3.taskId, t5.taskId),
+                List.of(t6.taskId)));
     }
 
     @Test
@@ -320,11 +323,11 @@
                         consumer);
         mMainExecutor.flushAll();
 
-        assertGroupedTasksListEquals(recentTasks[0],
-                t1.taskId, -1,
-                t2.taskId, t4.taskId,
-                t3.taskId, t5.taskId,
-                t6.taskId, -1);
+        assertGroupedTasksListEquals(recentTasks[0], List.of(
+                List.of(t1.taskId),
+                List.of(t2.taskId, t4.taskId),
+                List.of(t3.taskId, t5.taskId),
+                List.of(t6.taskId)));
     }
 
     @Test
@@ -343,9 +346,9 @@
 
         // 2 freeform tasks should be grouped into one, 3 total recents entries
         assertEquals(3, recentTasks.size());
-        GroupedTaskInfo freeformGroup = recentTasks.get(0);
-        GroupedTaskInfo singleGroup1 = recentTasks.get(1);
-        GroupedTaskInfo singleGroup2 = recentTasks.get(2);
+        GroupedTaskInfo singleGroup1 = recentTasks.get(0);
+        GroupedTaskInfo singleGroup2 = recentTasks.get(1);
+        GroupedTaskInfo freeformGroup = recentTasks.get(2);
 
         // Check that groups have expected types
         assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM));
@@ -383,8 +386,8 @@
         // 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries
         assertEquals(3, recentTasks.size());
         GroupedTaskInfo splitGroup = recentTasks.get(0);
-        GroupedTaskInfo freeformGroup = recentTasks.get(1);
-        GroupedTaskInfo singleGroup = recentTasks.get(2);
+        GroupedTaskInfo singleGroup = recentTasks.get(1);
+        GroupedTaskInfo freeformGroup = recentTasks.get(2);
 
         // Check that groups have expected types
         assertTrue(splitGroup.isBaseType(TYPE_SPLIT));
@@ -454,9 +457,9 @@
 
         // 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries
         assertEquals(3, recentTasks.size());
-        GroupedTaskInfo freeformGroup = recentTasks.get(0);
-        GroupedTaskInfo singleGroup1 = recentTasks.get(1);
-        GroupedTaskInfo singleGroup2 = recentTasks.get(2);
+        GroupedTaskInfo singleGroup1 = recentTasks.get(0);
+        GroupedTaskInfo singleGroup2 = recentTasks.get(1);
+        GroupedTaskInfo freeformGroup = recentTasks.get(2);
 
         // Check that groups have expected types
         assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM));
@@ -523,7 +526,7 @@
 
         // Remove one of the tasks and ensure the pair is removed
         SurfaceControl mockLeash = mock(SurfaceControl.class);
-        ActivityManager.RunningTaskInfo rt2 = makeRunningTaskInfo(2);
+        RunningTaskInfo rt2 = makeRunningTaskInfo(2);
         mShellTaskOrganizer.onTaskAppeared(rt2, mockLeash);
         mShellTaskOrganizer.onTaskVanished(rt2);
 
@@ -537,13 +540,13 @@
 
         // Remove one of the tasks and ensure the pair is removed
         SurfaceControl mockLeash = mock(SurfaceControl.class);
-        ActivityManager.RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2);
+        RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2);
         rt2Fullscreen.configuration.windowConfiguration.setWindowingMode(
                 WINDOWING_MODE_FULLSCREEN);
         mShellTaskOrganizer.onTaskAppeared(rt2Fullscreen, mockLeash);
 
         // Change the windowing mode and ensure the recent tasks change is notified
-        ActivityManager.RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2);
+        RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2);
         rt2MultiWIndow.configuration.windowConfiguration.setWindowingMode(
                 WINDOWING_MODE_MULTI_WINDOW);
         mShellTaskOrganizer.onTaskInfoChanged(rt2MultiWIndow);
@@ -557,7 +560,7 @@
     public void onTaskAdded_desktopModeRunningAppsEnabled_triggersOnRunningTaskAppeared()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskAdded(taskInfo);
 
@@ -570,7 +573,7 @@
     public void onTaskAdded_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskAppeared()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskAdded(taskInfo);
 
@@ -583,7 +586,7 @@
     public void taskWindowingModeChanged_desktopRunningAppsEnabled_triggersOnRunningTaskChanged()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
 
@@ -597,7 +600,7 @@
             taskWindowingModeChanged_desktopRunningAppsDisabled_doesNotTriggerOnRunningTaskChanged()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
 
@@ -610,7 +613,7 @@
     public void onTaskRemoved_desktopModeRunningAppsEnabled_triggersOnRunningTaskVanished()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskRemoved(taskInfo);
 
@@ -623,7 +626,7 @@
     public void onTaskRemoved_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskVanished()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskRemoved(taskInfo);
 
@@ -632,10 +635,11 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
     public void onTaskMovedToFront_TaskStackObserverEnabled_triggersOnTaskMovedToFront()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
 
@@ -648,7 +652,7 @@
     public void onTaskMovedToFront_TaskStackObserverEnabled_doesNotTriggersOnTaskMovedToFront()
             throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
 
         mRecentTasksControllerReal.onTaskMovedToFront(taskInfo);
 
@@ -700,7 +704,7 @@
     @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
     public void shellTopTaskTracker_onTaskRemoved_expectNoRecentsChanged() throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
         mRecentTasksControllerReal.onTaskRemoved(taskInfo);
         verify(mRecentTasksListener, never()).onRecentTasksChanged();
     }
@@ -710,22 +714,105 @@
     @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
     public void shellTopTaskTracker_onVisibleTasksChanged() throws Exception {
         mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
-        ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+        RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
         mRecentTasksControllerReal.onVisibleTasksChanged(List.of(taskInfo));
         verify(mRecentTasksListener, never()).onVisibleTasksChanged(any());
     }
 
+    @Test
+    public void generateList_emptyTaskList_expectNoGroupedTasks() throws Exception {
+        assertTrue(mRecentTasksControllerReal.generateList(List.of(), "test").isEmpty());
+    }
+
+    @Test
+    public void generateList_excludePipTask() throws Exception {
+        RunningTaskInfo task1 = makeRunningTaskInfo(1);
+        RunningTaskInfo pipTask = makeRunningTaskInfo(2);
+        pipTask.configuration.windowConfiguration.setWindowingMode(
+                WINDOWING_MODE_PINNED);
+
+        ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+                List.of(task1, pipTask),
+                "test");
+
+        assertGroupedTasksListEquals(groupedTasks, List.of(List.of(task1.taskId)));
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+    public void generateList_fullscreen_noVisibleTasks_expectNoGrouping() throws Exception {
+        RunningTaskInfo task1 = makeRunningTaskInfo(1);
+        RunningTaskInfo task2 = makeRunningTaskInfo(2);
+        RunningTaskInfo task3 = makeRunningTaskInfo(3);
+        // Reset visible tasks list
+        mRecentTasksControllerReal.onVisibleTasksChanged(List.of());
+
+        // Generate a list with a number of fullscreen tasks
+        ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+                List.of(task1, task2, task3),
+                "test");
+
+        assertGroupedTasksListEquals(groupedTasks, List.of(
+                List.of(task1.taskId),
+                List.of(task2.taskId),
+                List.of(task3.taskId)));
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+    public void generateList_fullscreen_singleVisibleTask_expectNoGrouping() throws Exception {
+        RunningTaskInfo task1 = makeRunningTaskInfo(1);
+        RunningTaskInfo task2 = makeRunningTaskInfo(2);
+        RunningTaskInfo task3 = makeRunningTaskInfo(3);
+
+        // Reset visible tasks list
+        mRecentTasksControllerReal.onVisibleTasksChanged(List.of(task1));
+
+        // Generate a list with a number of fullscreen tasks
+        ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+                List.of(task1, task2, task3),
+                "test");
+
+        assertGroupedTasksListEquals(groupedTasks, List.of(
+                List.of(task1.taskId),
+                List.of(task2.taskId),
+                List.of(task3.taskId)));
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+    public void generateList_fullscreen_multipleVisibleTasks_expectGrouping() throws Exception {
+        RunningTaskInfo task1 = makeRunningTaskInfo(1);
+        RunningTaskInfo task2 = makeRunningTaskInfo(2);
+        RunningTaskInfo task3 = makeRunningTaskInfo(3);
+
+        // Reset visible tasks list
+        mRecentTasksControllerReal.onVisibleTasksChanged(List.of(task1, task2));
+
+        // Generate a list with a number of fullscreen tasks
+        ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+                List.of(task1, task2, task3),
+                "test");
+
+        assertGroupedTasksListEquals(groupedTasks, List.of(
+                List.of(task1.taskId),
+                List.of(task2.taskId),
+                List.of(task3.taskId)));
+    }
+
     /**
      * Helper to create a task with a given task id.
      */
     private RecentTaskInfo makeTaskInfo(int taskId) {
         RecentTaskInfo info = new RecentTaskInfo();
         info.taskId = taskId;
-
+        info.realActivity = new ComponentName("testPackage", "testClass");
         Intent intent = new Intent();
         intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
         info.baseIntent = intent;
-
         info.lastNonFullscreenBounds = new Rect();
         return info;
     }
@@ -742,10 +829,14 @@
     /**
      * Helper to create a running task with a given task id.
      */
-    private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) {
-        ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
+    private RunningTaskInfo makeRunningTaskInfo(int taskId) {
+        RunningTaskInfo info = new RunningTaskInfo();
         info.taskId = taskId;
         info.realActivity = new ComponentName("testPackage", "testClass");
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
+        info.baseIntent = intent;
+        info.lastNonFullscreenBounds = new Rect();
         return info;
     }
 
@@ -759,37 +850,42 @@
 
     /**
      * Asserts that the recent tasks matches the given task ids.
+     * TODO(346588978): Separate out specific split verification during the iteration below
      *
-     * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
-     *                        the grouped task list
+     * @param expectedTaskIds a list of expected grouped task ids (itself a list of ints)
      */
-    private void assertGroupedTasksListEquals(List<GroupedTaskInfo> recentTasks,
-            int... expectedTaskIds) {
-        int[] flattenedTaskIds = new int[recentTasks.size() * 2];
-        for (int i = 0; i < recentTasks.size(); i++) {
-            GroupedTaskInfo pair = recentTasks.get(i);
-            int taskId1 = pair.getTaskInfo1().taskId;
-            flattenedTaskIds[2 * i] = taskId1;
-            flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null
-                    ? pair.getTaskInfo2().taskId
-                    : -1;
+    private void assertGroupedTasksListEquals(List<GroupedTaskInfo> groupedTasks,
+            List<List<Integer>> expectedTaskIds) {
+        List<List<Integer>> foundTaskIds = new ArrayList<>();
+        for (int i = 0; i < groupedTasks.size(); i++) {
+            GroupedTaskInfo groupedTask = groupedTasks.get(i);
+            List<Integer> groupedTaskIds = groupedTask.getTaskInfoList().stream()
+                    .map(taskInfo -> taskInfo.taskId)
+                    .toList();
+            foundTaskIds.add(groupedTaskIds);
 
-            if (pair.getTaskInfo2() != null) {
-                assertNotNull(pair.getSplitBounds());
-                int leftTopTaskId = pair.getSplitBounds().leftTopTaskId;
-                int bottomRightTaskId = pair.getSplitBounds().rightBottomTaskId;
+            if (groupedTask.isBaseType(TYPE_SPLIT)) {
+                assertNotNull(groupedTask.getSplitBounds());
+                int leftTopTaskId = groupedTask.getSplitBounds().leftTopTaskId;
+                int bottomRightTaskId = groupedTask.getSplitBounds().rightBottomTaskId;
                 // Unclear if pairs are ordered by split position, most likely not.
-                assertTrue(leftTopTaskId == taskId1
-                        || leftTopTaskId == pair.getTaskInfo2().taskId);
-                assertTrue(bottomRightTaskId == taskId1
-                        || bottomRightTaskId == pair.getTaskInfo2().taskId);
-            } else {
-                assertNull(pair.getSplitBounds());
+                assertTrue(leftTopTaskId == groupedTaskIds.getFirst()
+                        || leftTopTaskId == groupedTaskIds.getLast());
+                assertTrue(bottomRightTaskId == groupedTaskIds.getFirst()
+                        || bottomRightTaskId == groupedTaskIds.getLast());
             }
         }
-        assertArrayEquals("Expected: " + Arrays.toString(expectedTaskIds)
-                        + " Received: " + Arrays.toString(flattenedTaskIds),
-                flattenedTaskIds,
-                expectedTaskIds);
+        List<Integer> flattenedExpectedTaskIds = expectedTaskIds.stream()
+                .flatMap(List::stream)
+                .toList();
+        List<Integer> flattenedFoundTaskIds = foundTaskIds.stream()
+                .flatMap(List::stream)
+                .toList();
+        assertEquals("Expected: "
+                        + flattenedExpectedTaskIds.stream().map(String::valueOf).collect(joining())
+                        + " Received: "
+                        + flattenedFoundTaskIds.stream().map(String::valueOf).collect(joining()),
+                flattenedExpectedTaskIds,
+                flattenedFoundTaskIds);
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 66636c5..6ac34d7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -165,10 +165,11 @@
             doReturn(true).when(mTransitions).isRegistered();
         }
         mTaskViewRepository = new TaskViewRepository();
-        mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions, mTaskViewRepository));
+        mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions, mTaskViewRepository,
+                mOrganizer, mSyncQueue));
         mTaskViewTaskController = new TaskViewTaskController(mContext, mOrganizer,
                 mTaskViewTransitions, mSyncQueue);
-        mTaskView = new TaskView(mContext, mTaskViewTaskController);
+        mTaskView = new TaskView(mContext, mTaskViewTransitions, mTaskViewTaskController);
         mTaskView.setHandler(mViewHandler);
         mTaskView.setListener(mExecutor, mViewListener);
     }
@@ -182,7 +183,7 @@
 
     @Test
     public void testSetPendingListener_throwsException() {
-        TaskView taskView = new TaskView(mContext,
+        TaskView taskView = new TaskView(mContext, mTaskViewTransitions,
                 new TaskViewTaskController(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue));
         taskView.setListener(mExecutor, mViewListener);
         try {
@@ -326,7 +327,7 @@
     public void testOnNewTask_noSurface() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
 
@@ -354,7 +355,7 @@
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
 
@@ -366,7 +367,7 @@
     public void testSurfaceCreated_withTask() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -374,7 +375,7 @@
         verify(mViewListener).onInitialized();
         verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskViewTaskController), eq(true));
 
-        mTaskViewTaskController.prepareOpenAnimation(false /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, false /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
 
@@ -396,7 +397,7 @@
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         SurfaceHolder sh = mock(SurfaceHolder.class);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
         mTaskView.surfaceCreated(sh);
@@ -414,7 +415,7 @@
     public void testOnReleased() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -423,14 +424,14 @@
         verify(mOrganizer).removeListener(eq(mTaskViewTaskController));
         verify(mViewListener).onReleased();
         assertThat(mTaskView.isInitialized()).isFalse();
-        verify(mTaskViewTransitions).removeTaskView(eq(mTaskViewTaskController));
+        verify(mTaskViewTransitions).unregisterTaskView(eq(mTaskViewTaskController));
     }
 
     @Test
     public void testOnTaskVanished() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -443,7 +444,7 @@
     public void testOnBackPressedOnTaskRoot() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
         mTaskViewTaskController.onBackPressedOnTaskRoot(mTaskInfo);
@@ -455,7 +456,7 @@
     public void testSetOnBackPressedOnTaskRoot() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
         verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
@@ -524,7 +525,7 @@
 
         // Make the task available
         WindowContainerTransaction wct = mock(WindowContainerTransaction.class);
-        mTaskViewTaskController.startRootTask(mTaskInfo, mLeash, wct);
+        mTaskViewTransitions.startRootTask(mTaskViewTaskController, mTaskInfo, mLeash, wct);
 
         // Bounds got set
         verify(wct).setBounds(any(WindowContainerToken.class), eq(bounds));
@@ -564,7 +565,7 @@
 
         // Make the task available / start prepareOpen
         WindowContainerTransaction wct = mock(WindowContainerTransaction.class);
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
 
@@ -594,7 +595,7 @@
 
         // Task is available, but the surface was never created
         WindowContainerTransaction wct = mock(WindowContainerTransaction.class);
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
 
@@ -619,7 +620,7 @@
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
 
         mTaskView.removeTask();
-        verify(mTaskViewTransitions, never()).closeTaskView(any(), any());
+        assertFalse(mTaskViewTransitions.hasPending());
     }
 
     @Test
@@ -628,14 +629,14 @@
 
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
 
         verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
 
         mTaskView.removeTask();
-        verify(mTaskViewTransitions).closeTaskView(any(), eq(mTaskViewTaskController));
+        verify(mTaskViewTransitions).removeTaskView(eq(mTaskViewTaskController), any());
     }
 
     @Test
@@ -646,7 +647,7 @@
         mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
 
         assertNull(mTaskViewTaskController.getTaskInfo());
-        verify(mTaskViewTransitions).closeTaskView(any(), eq(mTaskViewTaskController));
+        verify(mTaskViewTransitions).removeTaskView(eq(mTaskViewTaskController), any());
     }
 
     @Test
@@ -655,7 +656,7 @@
 
         mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
         assertEquals(mTaskInfo, mTaskViewTaskController.getPendingInfo());
-        verify(mTaskViewTransitions, never()).closeTaskView(any(), any());
+        verify(mTaskViewTransitions, never()).removeTaskView(any(), any());
     }
 
     @Test
@@ -671,7 +672,7 @@
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
         reset(mOrganizer);
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
         mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
@@ -688,7 +689,7 @@
 
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
         reset(mOrganizer);
@@ -706,7 +707,7 @@
     public void testReleaseInOnTaskRemoval_noNPE() {
         mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer,
                 mTaskViewTransitions, mSyncQueue));
-        mTaskView = new TaskView(mContext, mTaskViewTaskController);
+        mTaskView = new TaskView(mContext, mTaskViewTransitions, mTaskViewTaskController);
         mTaskView.setListener(mExecutor, new TaskView.Listener() {
             @Override
             public void onTaskRemovalStarted(int taskId) {
@@ -715,7 +716,7 @@
         });
 
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
@@ -763,7 +764,7 @@
     @Test
     public void testOnAppeared_setsTrimmableTask() {
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
 
@@ -773,11 +774,11 @@
     @Test
     public void testMoveToFullscreen_callsTaskRemovalStarted() {
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+        mTaskViewTransitions.prepareOpenAnimation(mTaskViewTaskController, true /* newTask */,
                 new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
                 mLeash, wct);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
-        mTaskViewTaskController.moveToFullscreen();
+        mTaskViewTransitions.moveTaskViewToFullscreen(mTaskViewTaskController);
 
         verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId));
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 5f6f18f..326f11e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -44,7 +44,9 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.Flags;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -56,6 +58,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
 import platform.test.runner.parameterized.Parameters;
@@ -82,6 +85,12 @@
     ActivityManager.RunningTaskInfo mTaskInfo;
     @Mock
     WindowContainerToken mToken;
+    @Mock
+    ShellTaskOrganizer mOrganizer;
+    @Mock
+    SyncTransactionQueue mSyncQueue;
+
+    Executor mExecutor = command -> command.run();
 
     TaskViewRepository mTaskViewRepository;
     TaskViewTransitions mTaskViewTransitions;
@@ -104,9 +113,12 @@
         mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class);
 
         mTaskViewRepository = new TaskViewRepository();
-        mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions, mTaskViewRepository));
-        mTaskViewTransitions.addTaskView(mTaskViewTaskController);
+        when(mOrganizer.getExecutor()).thenReturn(mExecutor);
+        mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions, mTaskViewRepository,
+                mOrganizer, mSyncQueue));
+        mTaskViewTransitions.registerTaskView(mTaskViewTaskController);
         when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
+        when(mTaskViewTaskController.getTaskToken()).thenReturn(mToken);
     }
 
     @Test
@@ -212,7 +224,7 @@
 
     @Test
     public void testSetTaskVisibility_taskRemoved_noNPE() {
-        mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+        mTaskViewTransitions.unregisterTaskView(mTaskViewTaskController);
 
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
 
@@ -221,7 +233,7 @@
 
     @Test
     public void testSetTaskBounds_taskRemoved_noNPE() {
-        mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+        mTaskViewTransitions.unregisterTaskView(mTaskViewTaskController);
 
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 79e9b9c..b479164 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -61,6 +61,7 @@
 import com.android.wm.shell.R
 import com.android.wm.shell.desktopmode.DesktopImmersiveController
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
 import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
@@ -272,7 +273,7 @@
 
         onClickListenerCaptor.value.onClick(view)
 
-        verify(mockDesktopTasksController).minimizeTask(decor.mTaskInfo)
+        verify(mockDesktopTasksController).minimizeTask(decor.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index b44af47..908bc99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -31,6 +31,7 @@
 import android.testing.TestableContext
 import android.util.SparseArray
 import android.view.Choreographer
+import android.view.Display
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.IWindowManager
 import android.view.InputChannel
@@ -157,6 +158,7 @@
     protected val mockRecentsTransitionHandler = mock<RecentsTransitionHandler>()
     protected val motionEvent = mock<MotionEvent>()
     val displayLayout = mock<DisplayLayout>()
+    val display = mock<Display>()
     protected lateinit var spyContext: TestableContext
     private lateinit var desktopModeEventLogger: DesktopModeEventLogger
 
@@ -183,6 +185,7 @@
         desktopModeEventLogger = mock<DesktopModeEventLogger>()
         whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
         whenever(mockDisplayController.getDisplayContext(any())).thenReturn(spyContext)
+        whenever(mockDisplayController.getDisplay(any())).thenReturn(display)
         whenever(mockDesktopUserRepositories.getProfile(anyInt()))
             .thenReturn(mockDesktopRepository)
         desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 302969f..9bb31d0 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -908,13 +908,13 @@
         /** @hide */
         public String[] validFeatures() {
             Feature[] features = getValidFeatures();
-            String[] res = new String[features.length];
-            for (int i = 0; i < res.length; i++) {
+            ArrayList<String> res = new ArrayList();
+            for (int i = 0; i < features.length; i++) {
                 if (!features[i].mInternal) {
-                    res[i] = features[i].mName;
+                    res.add(features[i].mName);
                 }
             }
-            return res;
+            return res.toArray(new String[0]);
         }
 
         private Feature[] getValidFeatures() {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index c48b5f4..312f78e 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -173,6 +173,18 @@
     bug: "281072508"
 }
 
+
+flag {
+    name: "enable_singleton_audio_manager_route_controller"
+    is_exported: true
+    namespace: "media_solutions"
+    description: "Use singleton AudioManagerRouteController shared across all users."
+    bug: "372868909"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
 flag {
     name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
     namespace: "media_solutions"
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index abfc244..f352a41 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -161,11 +161,6 @@
             new RemoteCallbackList<>();
 
     private TvInputManager mTvInputManager;
-    /**
-     * @hide
-     */
-    protected TvInputServiceExtensionManager mTvInputServiceExtensionManager =
-            new TvInputServiceExtensionManager();
 
     @Override
     public final IBinder onBind(Intent intent) {
@@ -230,12 +225,20 @@
 
             @Override
             public IBinder getExtensionInterface(String name) {
-                if (tifExtensionStandardization() && name != null) {
-                    if (TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) {
-                        return mTvInputServiceExtensionManager.getExtensionIBinder(name);
+                IBinder binder = TvInputService.this.getExtensionInterface(name);
+                if (tifExtensionStandardization()) {
+                    if (name != null
+                            && TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) {
+                        if (TvInputServiceExtensionManager.checkIsStandardizedIBinder(name,
+                                binder)) {
+                            return binder;
+                        } else {
+                            // binder with standardized name is not standardized
+                            return null;
+                        }
                     }
                 }
-                return TvInputService.this.getExtensionInterface(name);
+                return binder;
             }
 
             @Override
diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java
index d33ac92..02d2616 100644
--- a/media/java/android/media/tv/TvInputServiceExtensionManager.java
+++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java
@@ -16,13 +16,9 @@
 
 package android.media.tv;
 
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
 import android.annotation.StringDef;
-import android.media.tv.flags.Flags;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -30,21 +26,17 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 
 /**
  * This class provides a list of available standardized TvInputService extension interface names
- * and a container storing IBinder objects that implement these interfaces created by SoC/OEMs.
- * It also provides an API for SoC/OEMs to register implemented IBinder objects.
+ * and checks if IBinder objects created by SoC/OEMs implement these interfaces.
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION)
 public final class TvInputServiceExtensionManager {
     private static final String TAG = "TvInputServiceExtensionManager";
     private static final String SCAN_PACKAGE = "android.media.tv.extension.scan.";
@@ -63,33 +55,6 @@
     private static final String ANALOG_PACKAGE = "android.media.tv.extension.analog.";
     private static final String TUNE_PACKAGE = "android.media.tv.extension.tune.";
 
-    @IntDef(prefix = {"REGISTER_"}, value = {
-            REGISTER_SUCCESS,
-            REGISTER_FAIL_NAME_NOT_STANDARDIZED,
-            REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED,
-            REGISTER_FAIL_REMOTE_EXCEPTION
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RegisterResult {}
-
-    /**
-     * Registering binder returns success when it abides standardized interface structure
-     */
-    public static final int REGISTER_SUCCESS = 0;
-    /**
-     * Registering binder returns failure when the extension name is not in the standardization
-     * list
-     */
-    public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1;
-    /**
-     * Registering binder returns failure when the IBinder does not implement standardized interface
-     */
-    public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2;
-    /**
-     * Registering binder returns failure when remote server is not available
-     */
-    public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3;
-
     @StringDef({
             ISCAN_INTERFACE,
             ISCAN_SESSION,
@@ -673,12 +638,6 @@
             IMUX_TUNE
     ));
 
-    // Store the mapping between interface names and IBinder
-    private Map<String, IBinder> mExtensionInterfaceIBinderMapping = new HashMap<>();
-
-    TvInputServiceExtensionManager() {
-    }
-
     /**
      * Function to return available extension interface names
      */
@@ -694,43 +653,18 @@
     }
 
     /**
-     * Registers IBinder objects that implement standardized AIDL interfaces.
-     * <p>This function should be used by SoCs/OEMs
-     *
-     * @param extensionName Extension Interface Name
-     * @param binder        IBinder object to be registered
-     * @return {@link #REGISTER_SUCCESS} on success of registering IBinder object
-     *         {@link #REGISTER_FAIL_NAME_NOT_STANDARDIZED} on failure due to registering extension
-     *              with non-standardized name
-     *         {@link #REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED} on failure due to IBinder not
-     *              implementing standardized AIDL interface
-     *         {@link #REGISTER_FAIL_REMOTE_EXCEPTION} on failure due to remote exception
+     * Function check if the IBinder object implements standardized interface
      */
-    @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
-    @RegisterResult
-    public int registerExtensionIBinder(@StandardizedExtensionName @NonNull String extensionName,
-            @NonNull IBinder binder) {
-        if (!checkIsStandardizedInterfaces(extensionName)) {
-            return REGISTER_FAIL_NAME_NOT_STANDARDIZED;
-        }
-        try {
-            if (binder.getInterfaceDescriptor().equals(extensionName)) {
-                mExtensionInterfaceIBinderMapping.put(extensionName, binder);
-                return REGISTER_SUCCESS;
-            } else {
-                return REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED;
+    public static boolean checkIsStandardizedIBinder(@NonNull String extensionName,
+            @Nullable IBinder binder) {
+        if (binder != null) {
+            try {
+                return binder.getInterfaceDescriptor().equals(extensionName);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Fetching IBinder object failure due to " + e);
             }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Fetching IBinder object failure due to " + e);
-            return REGISTER_FAIL_REMOTE_EXCEPTION;
         }
-    }
-
-    /**
-     * Function to get corresponding IBinder object
-     */
-    @Nullable IBinder getExtensionIBinder(@NonNull String extensionName) {
-        return mExtensionInterfaceIBinderMapping.get(extensionName);
+        return false;
     }
 
 }
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml
new file mode 100644
index 0000000..ec9ee22
--- /dev/null
+++ b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_background.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:color="@color/settingslib_materialColorSurface"/>
+    <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+    <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+    <item android:color="@color/settingslib_materialColorPrimaryContainer" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.xml b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.xml
new file mode 100644
index 0000000..0488cba
--- /dev/null
+++ b/packages/SettingsLib/ActionButtonsPreference/res/color/settingslib_expressive_actionbutton_content_color.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:alpha="@dimen/material_emphasis_disabled" android:color="?attr/colorOnSurface"/>
+    <item android:state_checkable="true" android:state_checked="true"
+        android:color="?attr/colorOnContainerChecked"/>
+    <item android:state_checkable="true" android:color="?attr/colorOnContainerUnchecked"/>
+    <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
index fd8cecb..267c9f6 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
@@ -17,9 +17,14 @@
 
 <resources>
     <style name="SettingsLibActionButton.Expressive" parent="SettingsLibButtonStyle.Expressive.Tonal">
-        <item name="android:backgroundTint">@color/settingslib_materialColorPrimaryContainer</item>
-        <item name="iconTint">@color/settingslib_materialColorOnPrimaryContainer</item>
-        <item name="iconGravity">textTop</item>
+        <item name="android:backgroundTint">@color/settingslib_expressive_actionbutton_background</item>
+        <item name="android:textColor">@color/settingslib_expressive_actionbutton_content_color</item>
+        <item name="android:insetTop">@dimen/settingslib_expressive_space_none</item>
+        <item name="android:insetBottom">@dimen/settingslib_expressive_space_none</item>
+        <item name="iconTint">@color/settingslib_expressive_actionbutton_content_color</item>
+        <item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
+        <item name="iconPadding">@dimen/settingslib_expressive_space_none</item>"
+        <item name="iconGravity">textStart</item>
     </style>
 
     <style name="SettingsLibActionButton.Expressive.Label" parent="">
diff --git a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
index 601e001..0027d63 100644
--- a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
+++ b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
@@ -549,7 +549,7 @@
                     ((MaterialButton) mButton).setIcon(mIcon);
                 }
                 mButton.setEnabled(mIsEnabled);
-                mActionLayout.setOnClickListener(mListener);
+                mButton.setOnClickListener(mListener);
                 mActionLayout.setEnabled(mIsEnabled);
                 mActionLayout.setContentDescription(mText);
             } else {
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 933c512..e5b5837 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -46,6 +46,8 @@
         "SettingsLibIntroPreference",
         "SettingsLibLayoutPreference",
         "SettingsLibMainSwitchPreference",
+        "SettingsLibMetadata",
+        "SettingsLibPreference",
         "SettingsLibProfileSelector",
         "SettingsLibProgressBar",
         "SettingsLibRestrictedLockUtils",
@@ -77,6 +79,7 @@
         "src/**/*.kt",
         "src/**/I*.aidl",
     ],
+    kotlincflags: ["-Xjvm-default=all"],
 }
 
 // defaults for lint option
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index 3f671b9..77e2cc7 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -14,12 +14,16 @@
         "SettingsLintDefaults",
     ],
 
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     resource_dirs: ["res"],
 
     static_libs: [
-        "androidx.preference_preference",
+        "SettingsLibButtonPreference",
         "SettingsLibSettingsTheme",
+        "androidx.preference_preference",
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml
new file mode 100644
index 0000000..d113b547
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_colorBackgroundLevel_high"/>
+    <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+    <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+    <item android:color="@color/settingslib_colorBackgroundLevel_high" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml
new file mode 100644
index 0000000..cb89d9a
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_colorBackgroundLevel_low"/>
+    <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+    <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+    <item android:color="@color/settingslib_colorBackgroundLevel_low" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml
new file mode 100644
index 0000000..f820c35
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_colorBackgroundLevel_medium"/>
+    <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+    <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+    <item android:color="@color/settingslib_colorBackgroundLevel_medium" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml
new file mode 100644
index 0000000..8037a8b
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:alpha="@dimen/material_emphasis_disabled_background" android:color="?attr/colorOnSurface"/>
+    <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+    <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+    <item android:color="?attr/colorContainer" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml
index 072eb58..3f806e1 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml
@@ -16,6 +16,6 @@
   -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?android:attr/background" />
+    <solid android:color="@android:color/white" />
     <corners android:radius="28dp"/>
 </shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml
new file mode 100644
index 0000000..a677a66
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/settingslib_materialColorSurfaceBright" />
+    <corners android:radius="28dp"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml
index 9d53e39..ca596d8 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml
@@ -15,85 +15,92 @@
   limitations under the License.
   -->
 
-<com.android.settingslib.widget.BannerMessageView
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical"
-    style="@style/Banner.Preference.SettingsLib">
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart">
 
-    <RelativeLayout
-        android:id="@+id/top_row"
+    <com.android.settingslib.widget.BannerMessageView
+        android:id="@+id/banner_background"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingBottom="8dp"
-        android:orientation="horizontal">
+        android:orientation="vertical"
+        style="@style/Banner.Preference.SettingsLib">
 
-        <ImageView
-            android:id="@+id/banner_icon"
-            android:layout_width="24dp"
-            android:layout_height="24dp"
-            android:layout_alignParentStart="true"
-            android:importantForAccessibility="no" />
+        <RelativeLayout
+            android:id="@+id/top_row"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="8dp"
+            android:orientation="horizontal">
 
-        <ImageButton
-            android:id="@+id/banner_dismiss_btn"
+            <ImageView
+                android:id="@+id/banner_icon"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:layout_alignParentStart="true"
+                android:importantForAccessibility="no" />
+
+            <ImageButton
+                android:id="@+id/banner_dismiss_btn"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/settingslib_ic_cross"
+                android:layout_alignParentEnd="true"
+                android:contentDescription="@string/accessibility_banner_message_dismiss"
+                style="@style/Banner.Dismiss.SettingsLib" />
+        </RelativeLayout>
+
+        <TextView
+            android:id="@+id/banner_title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:src="@drawable/settingslib_ic_cross"
-            android:layout_alignParentEnd="true"
-            android:contentDescription="@string/accessibility_banner_message_dismiss"
-            style="@style/Banner.Dismiss.SettingsLib" />
-    </RelativeLayout>
+            android:layout_gravity="start"
+            android:textAlignment="viewStart"
+            android:paddingTop="0dp"
+            android:paddingBottom="4dp"
+            android:textAppearance="@style/Banner.Title.SettingsLib"/>
 
-    <TextView
-        android:id="@+id/banner_title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="start"
-        android:textAlignment="viewStart"
-        android:paddingTop="0dp"
-        android:paddingBottom="4dp"
-        android:textAppearance="@style/Banner.Title.SettingsLib"/>
-
-    <TextView
-        android:id="@+id/banner_subtitle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="start"
-        android:textAlignment="viewStart"
-        android:paddingTop="0dp"
-        android:paddingBottom="4dp"
-        android:textAppearance="@style/Banner.Subtitle.SettingsLib"
-        android:visibility="gone"/>
-
-    <TextView
-        android:id="@+id/banner_summary"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="start"
-        android:textAlignment="viewStart"
-        android:paddingTop="4dp"
-        android:paddingBottom="8dp"
-        android:textAppearance="@style/Banner.Summary.SettingsLib"/>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:minHeight="8dp"
-        android:gravity="end">
-
-        <Button
-            android:id="@+id/banner_negative_btn"
+        <TextView
+            android:id="@+id/banner_subtitle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            style="@style/Banner.ButtonText.SettingsLib"/>
+            android:layout_gravity="start"
+            android:textAlignment="viewStart"
+            android:paddingTop="0dp"
+            android:paddingBottom="4dp"
+            android:textAppearance="@style/Banner.Subtitle.SettingsLib"
+            android:visibility="gone"/>
 
-        <Button
-            android:id="@+id/banner_positive_btn"
+        <TextView
+            android:id="@+id/banner_summary"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            style="@style/Banner.ButtonText.SettingsLib"/>
-    </LinearLayout>
-</com.android.settingslib.widget.BannerMessageView>
\ No newline at end of file
+            android:layout_gravity="start"
+            android:textAlignment="viewStart"
+            android:paddingTop="4dp"
+            android:paddingBottom="8dp"
+            android:textAppearance="@style/Banner.Summary.SettingsLib"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:minHeight="8dp"
+            android:gravity="end">
+
+            <Button
+                android:id="@+id/banner_negative_btn"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                style="@style/Banner.ButtonText.SettingsLib"/>
+
+            <Button
+                android:id="@+id/banner_positive_btn"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                style="@style/Banner.ButtonText.SettingsLib"/>
+        </LinearLayout>
+    </com.android.settingslib.widget.BannerMessageView>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
new file mode 100644
index 0000000..b10ef6e
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+    <com.android.settingslib.widget.BannerMessageView
+        android:id="@+id/banner_background"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        style="@style/Banner.Preference.SettingsLib.Expressive">
+
+        <RelativeLayout
+            android:id="@+id/top_row"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4"
+                android:orientation="vertical">
+                <TextView
+                    android:id="@+id/banner_header"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    style="@style/Banner.Header.SettingsLib.Expressive"/>
+
+                <LinearLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content">
+
+                    <ImageView
+                        android:id="@+id/banner_icon"
+                        android:layout_width="@dimen/settingslib_expressive_space_small3"
+                        android:layout_height="@dimen/settingslib_expressive_space_small3"
+                        android:layout_gravity="center_vertical"
+                        android:importantForAccessibility="no"
+                        android:scaleType="fitCenter" />
+
+                    <TextView
+                        android:id="@+id/banner_title"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        style="@style/Banner.Title.SettingsLib.Expressive" />
+                </LinearLayout>
+
+                <TextView
+                    android:id="@+id/banner_subtitle"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    style="@style/Banner.Subtitle.SettingsLib.Expressive"/>
+            </LinearLayout>
+
+            <ImageButton
+                android:id="@+id/banner_dismiss_btn"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                style="@style/Banner.Dismiss.SettingsLib.Expressive" />
+        </RelativeLayout>
+
+        <TextView
+            android:id="@+id/banner_summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/Banner.Summary.SettingsLib.Expressive"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/banner_buttons_frame"
+            android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
+            android:orientation="horizontal">
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/banner_negative_btn"
+                android:layout_weight="1"
+                style="@style/Banner.NegativeButton.SettingsLib.Expressive"/>
+            <Space
+                android:layout_width="@dimen/settingslib_expressive_space_extrasmall4"
+                android:layout_height="@dimen/settingslib_expressive_space_small1"/>
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/banner_positive_btn"
+                android:layout_weight="1"
+                style="@style/Banner.PositiveButton.SettingsLib.Expressive"/>
+        </LinearLayout>
+    </com.android.settingslib.widget.BannerMessageView>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml b/packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml
new file mode 100644
index 0000000..c74e391
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:baselineAligned="false"
+    android:id="@+id/banner_group_layout"
+    android:importantForAccessibility="no"
+    android:filterTouchesWhenObscured="false"
+    android:orientation="horizontal">
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
index fede44f..5909f8e 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
@@ -24,9 +24,7 @@
         <item name="android:paddingTop">20dp</item>
         <item name="android:paddingBottom">8dp</item>
         <item name="android:layout_marginTop">8dp</item>
-        <item name="android:layout_marginStart">16dp</item>
         <item name="android:layout_marginBottom">8dp</item>
-        <item name="android:layout_marginEnd">16dp</item>
         <item name="android:background">@drawable/settingslib_card_background</item>
     </style>
 
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
new file mode 100644
index 0000000..b864311
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<resources>
+
+    <style name="Banner.Preference.SettingsLib.Expressive">
+        <item name="android:padding">@dimen/settingslib_expressive_space_small1</item>
+        <item name="android:background">@drawable/settingslib_expressive_card_background</item>
+    </style>
+
+    <style name="Banner.Header.SettingsLib.Expressive"
+        parent="">
+        <item name="android:textAlignment">viewStart</item>
+        <item name="android:paddingBottom">@dimen/settingslib_expressive_space_extrasmall4</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelMedium</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="Banner.Title.SettingsLib.Expressive"
+        parent="">
+        <item name="android:layout_gravity">start</item>
+        <item name="android:layout_marginLeft">@dimen/settingslib_expressive_space_extrasmall4</item>
+        <item name="android:textAlignment">viewStart</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="Banner.Subtitle.SettingsLib.Expressive"
+        parent="">
+        <item name="android:layout_gravity">start</item>
+        <item name="android:textAlignment">viewStart</item>
+        <item name="android:paddingTop">@dimen/settingslib_expressive_space_extrasmall4</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="Banner.Summary.SettingsLib.Expressive"
+        parent="">
+        <item name="android:layout_gravity">start</item>
+        <item name="android:textAlignment">viewStart</item>
+        <item name="android:paddingTop">@dimen/settingslib_expressive_space_extrasmall6</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="Banner.Dismiss.SettingsLib.Expressive">
+        <item name="android:src">@drawable/settingslib_expressive_icon_cross</item>
+        <item name="android:layout_alignParentEnd">true</item>
+        <item name="android:contentDescription">@string/accessibility_banner_message_dismiss</item>
+    </style>
+
+    <style name="Banner.PositiveButton.SettingsLib.Expressive"
+        parent="@style/SettingsLibButtonStyle.Expressive.Filled.Extra">
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+        <item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
+    </style>
+
+    <style name="Banner.NegativeButton.SettingsLib.Expressive"
+        parent="@style/SettingsLibButtonStyle.Expressive.Outline.Extra">
+        <item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
+    </style>
+</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml b/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml
index 96634a5..86d5f47 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml
@@ -21,7 +21,18 @@
             <enum name="high" value="0"/>
             <enum name="medium" value="1"/>
             <enum name="low" value="2"/>
+            <enum name="normal" value="3"/>
         </attr>
         <attr format="string" name="subtitle" />
+        <attr format="string" name="bannerHeader" />
+        <attr format="integer" name="buttonOrientation" />
+    </declare-styleable>
+
+    <declare-styleable name="BannerMessagePreferenceGroup">
+        <attr format="string" name="expandKey" />
+        <attr format="string" name="expandTitle" />
+        <attr format="string" name="collapseKey" />
+        <attr format="string" name="collapseTitle" />
+        <attr format="reference" name="collapseIcon" />
     </declare-styleable>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml b/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml
index 53d72d1..891def1 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml
@@ -19,7 +19,9 @@
     <color name="banner_background_attention_high">#FFDAD5</color> <!-- red card background -->
     <color name="banner_background_attention_medium">#F0E3A8</color> <!-- yellow card background -->
     <color name="banner_background_attention_low">#CFEBC0</color> <!-- green card background -->
+    <color name="banner_background_attention_normal">@color/settingslib_materialColorSurfaceBright</color> <!-- normal card background -->
     <color name="banner_accent_attention_high">#BB3322</color> <!-- red accent color -->
     <color name="banner_accent_attention_medium">#895900</color> <!-- yellow accent color -->
     <color name="banner_accent_attention_low">#1D7233</color> <!-- green accent color -->
+    <color name="banner_accent_attention_normal">@color/settingslib_materialColorPrimary</color> <!-- normal accent color -->
 </resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
index 10769ec..60a9ebd 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -17,6 +17,7 @@
 package com.android.settingslib.widget;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.PorterDuff;
@@ -29,6 +30,7 @@
 import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.annotation.ColorInt;
@@ -39,6 +41,8 @@
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.settingslib.widget.preference.banner.R;
+
+import com.google.android.material.button.MaterialButton;
 /**
  * Banner message is a banner displaying important information (permission request, page error etc),
  * and provide actions for user to address. It requires a user action to be dismissed.
@@ -46,22 +50,36 @@
 public class BannerMessagePreference extends Preference implements GroupSectionDividerMixin {
 
     public enum AttentionLevel {
-        HIGH(0, R.color.banner_background_attention_high, R.color.banner_accent_attention_high),
+        HIGH(0,
+                R.color.banner_background_attention_high,
+                R.color.banner_accent_attention_high,
+                R.color.settingslib_banner_button_background_high),
         MEDIUM(1,
-               R.color.banner_background_attention_medium,
-               R.color.banner_accent_attention_medium),
-        LOW(2, R.color.banner_background_attention_low, R.color.banner_accent_attention_low);
+                R.color.banner_background_attention_medium,
+                R.color.banner_accent_attention_medium,
+                R.color.settingslib_banner_button_background_medium),
+        LOW(2,
+                R.color.banner_background_attention_low,
+                R.color.banner_accent_attention_low,
+                R.color.settingslib_banner_button_background_low),
+        NORMAL(3,
+                R.color.banner_background_attention_normal,
+                R.color.banner_accent_attention_normal,
+                R.color.settingslib_banner_button_background_normal);
 
         // Corresponds to the enum valye of R.attr.attentionLevel
         private final int mAttrValue;
         @ColorRes private final int mBackgroundColorResId;
         @ColorRes private final int mAccentColorResId;
+        @ColorRes private final int mButtonBackgroundColorResId;
 
         AttentionLevel(int attrValue, @ColorRes int backgroundColorResId,
-                @ColorRes int accentColorResId) {
+                @ColorRes int accentColorResId,
+                @ColorRes int buttonBackgroundColorResId) {
             mAttrValue = attrValue;
             mBackgroundColorResId = backgroundColorResId;
             mAccentColorResId = accentColorResId;
+            mButtonBackgroundColorResId = buttonBackgroundColorResId;
         }
 
         static AttentionLevel fromAttr(int attrValue) {
@@ -80,6 +98,10 @@
         public @ColorRes int getBackgroundColorResId() {
             return mBackgroundColorResId;
         }
+
+        public @ColorRes int getButtonBackgroundColorResId() {
+            return mButtonBackgroundColorResId;
+        }
     }
 
     private static final String TAG = "BannerPreference";
@@ -95,6 +117,8 @@
     // Default attention level is High.
     private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
     private String mSubtitle;
+    private String mHeader;
+    private int mButtonOrientation;
 
     public BannerMessagePreference(Context context) {
         super(context);
@@ -119,7 +143,10 @@
 
     private void init(Context context, AttributeSet attrs) {
         setSelectable(false);
-        setLayoutResource(R.layout.settingslib_banner_message);
+        int resId = SettingsThemeHelper.isExpressiveTheme(context)
+                ? R.layout.settingslib_expressive_banner_message
+                : R.layout.settingslib_banner_message;
+        setLayoutResource(resId);
 
         if (IS_AT_LEAST_S) {
             if (attrs != null) {
@@ -130,6 +157,9 @@
                         a.getInt(R.styleable.BannerMessagePreference_attentionLevel, 0);
                 mAttentionLevel = AttentionLevel.fromAttr(mAttentionLevelValue);
                 mSubtitle = a.getString(R.styleable.BannerMessagePreference_subtitle);
+                mHeader = a.getString(R.styleable.BannerMessagePreference_bannerHeader);
+                mButtonOrientation = a.getInt(R.styleable.BannerMessagePreference_buttonOrientation,
+                        LinearLayout.HORIZONTAL);
                 a.recycle();
             }
         }
@@ -142,11 +172,16 @@
 
         final TextView titleView = (TextView) holder.findViewById(R.id.banner_title);
         CharSequence title = getTitle();
-        titleView.setText(title);
-        titleView.setVisibility(title == null ? View.GONE : View.VISIBLE);
+        if (titleView != null) {
+            titleView.setText(title);
+            titleView.setVisibility(title == null ? View.GONE : View.VISIBLE);
+        }
 
         final TextView summaryView = (TextView) holder.findViewById(R.id.banner_summary);
-        summaryView.setText(getSummary());
+        if (summaryView != null) {
+            summaryView.setText(getSummary());
+            summaryView.setVisibility(TextUtils.isEmpty(getSummary()) ? View.GONE : View.VISIBLE);
+        }
 
         mPositiveButtonInfo.mButton = (Button) holder.findViewById(R.id.banner_positive_btn);
         mNegativeButtonInfo.mButton = (Button) holder.findViewById(R.id.banner_negative_btn);
@@ -162,8 +197,11 @@
                     icon == null
                             ? getContext().getDrawable(R.drawable.ic_warning)
                             : icon);
-            iconView.setColorFilter(
-                    new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+            if (mAttentionLevel != AttentionLevel.NORMAL
+                    && !SettingsThemeHelper.isExpressiveTheme(context)) {
+                iconView.setColorFilter(
+                        new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+            }
         }
 
         if (IS_AT_LEAST_S) {
@@ -171,12 +209,25 @@
                     context.getResources().getColor(
                             mAttentionLevel.getBackgroundColorResId(), theme);
 
+            @ColorInt final int btnBackgroundColor =
+                    context.getResources().getColor(mAttentionLevel.getButtonBackgroundColorResId(),
+                            theme);
+            ColorStateList strokeColor = context.getResources().getColorStateList(
+                    mAttentionLevel.getButtonBackgroundColorResId(), theme);
+
             holder.setDividerAllowedAbove(false);
             holder.setDividerAllowedBelow(false);
-            holder.itemView.getBackground().setTint(backgroundColor);
+            View backgroundView = holder.findViewById(R.id.banner_background);
+            if (backgroundView != null && !SettingsThemeHelper.isExpressiveTheme(context)) {
+                backgroundView.getBackground().setTint(backgroundColor);
+            }
 
             mPositiveButtonInfo.mColor = accentColor;
             mNegativeButtonInfo.mColor = accentColor;
+            if (mAttentionLevel != AttentionLevel.NORMAL) {
+                mPositiveButtonInfo.mBackgroundColor = btnBackgroundColor;
+                mNegativeButtonInfo.mStrokeColor = strokeColor;
+            }
 
             mDismissButtonInfo.mButton = (ImageButton) holder.findViewById(R.id.banner_dismiss_btn);
             mDismissButtonInfo.setUpButton();
@@ -185,6 +236,13 @@
             subtitleView.setText(mSubtitle);
             subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE);
 
+            TextView headerView = (TextView) holder.findViewById(R.id.banner_header);
+            if (headerView != null) {
+                headerView.setText(mHeader);
+                headerView.setVisibility(TextUtils.isEmpty(mHeader) ? View.GONE : View.VISIBLE);
+            }
+
+
         } else {
             holder.setDividerAllowedAbove(true);
             holder.setDividerAllowedBelow(true);
@@ -192,6 +250,24 @@
 
         mPositiveButtonInfo.setUpButton();
         mNegativeButtonInfo.setUpButton();
+        View buttonFrame = holder.findViewById(R.id.banner_buttons_frame);
+        if (buttonFrame != null) {
+            buttonFrame.setVisibility(
+                    mPositiveButtonInfo.shouldBeVisible() || mNegativeButtonInfo.shouldBeVisible()
+                            ? View.VISIBLE : View.GONE);
+
+            LinearLayout linearLayout = (LinearLayout) buttonFrame;
+            if (mButtonOrientation != linearLayout.getOrientation()) {
+                int childCount = linearLayout.getChildCount();
+                //reverse the order of the buttons
+                for (int i = childCount - 1; i >= 0; i--) {
+                    View child = linearLayout.getChildAt(i);
+                    linearLayout.removeViewAt(i);
+                    linearLayout.addView(child);
+                }
+                linearLayout.setOrientation(mButtonOrientation);
+            }
+        }
     }
 
     /**
@@ -302,6 +378,18 @@
     }
 
     /**
+     * Sets button orientation.
+     */
+    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public BannerMessagePreference setButtonOrientation(int orientation) {
+        if (mButtonOrientation != orientation) {
+            mButtonOrientation = orientation;
+            notifyChanged();
+        }
+        return this;
+    }
+
+    /**
      * Sets the subtitle.
      */
     @RequiresApi(Build.VERSION_CODES.S)
@@ -322,6 +410,26 @@
     }
 
     /**
+     * Sets the header.
+     */
+    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public BannerMessagePreference setHeader(@StringRes int textResId) {
+        return setHeader(getContext().getString(textResId));
+    }
+
+    /**
+     * Sets the header.
+     */
+    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public BannerMessagePreference setHeader(String header) {
+        if (!TextUtils.equals(header, mSubtitle)) {
+            mHeader = header;
+            notifyChanged();
+        }
+        return this;
+    }
+
+    /**
      * Sets the attention level. This will update the color theme of the preference.
      */
     public BannerMessagePreference setAttentionLevel(AttentionLevel attentionLevel) {
@@ -342,13 +450,29 @@
         private View.OnClickListener mListener;
         private boolean mIsVisible = true;
         @ColorInt private int mColor;
+        @ColorInt private int mBackgroundColor;
+        private ColorStateList mStrokeColor;
 
         void setUpButton() {
             mButton.setText(mText);
             mButton.setOnClickListener(mListener);
 
+            MaterialButton btn = null;
+            if (mButton instanceof MaterialButton) {
+                btn = (MaterialButton) mButton;
+            }
+
             if (IS_AT_LEAST_S) {
-                mButton.setTextColor(mColor);
+                if (btn != null && SettingsThemeHelper.isExpressiveTheme(btn.getContext())) {
+                    if (mBackgroundColor != 0) {
+                        btn.setBackgroundColor(mBackgroundColor);
+                    }
+                    if (mStrokeColor != null) {
+                        btn.setStrokeColor(mStrokeColor);
+                    }
+                } else {
+                    mButton.setTextColor(mColor);
+                }
             }
 
             if (shouldBeVisible()) {
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt
new file mode 100644
index 0000000..7545563
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.settingslib.widget
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.View
+import androidx.preference.Preference
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceViewHolder
+
+import com.android.settingslib.widget.preference.banner.R
+
+/**
+ * Custom PreferenceGroup that allows expanding and collapsing child preferences.
+ */
+class BannerMessagePreferenceGroup @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : PreferenceGroup(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
+
+    private var isExpanded = false
+    private var expandPreference: NumberButtonPreference? = null
+    private var collapsePreference: SectionButtonPreference? = null
+    private val childPreferences = mutableListOf<BannerMessagePreference>()
+    private var expandKey: String? = null
+    private var expandTitle: String? = null
+    private var collapseKey: String? = null
+    private var collapseTitle: String? = null
+    private var collapseIcon: Drawable? = null
+    var expandContentDescription: Int = 0
+        set(value) {
+            field = value
+            expandPreference?.btnContentDescription = expandContentDescription
+        }
+
+    init {
+        isPersistent = false // This group doesn't store data
+        layoutResource = R.layout.settingslib_banner_message_preference_group
+
+        initAttributes(context, attrs, defStyleAttr)
+    }
+
+    override fun addPreference(preference: Preference): Boolean {
+        if (preference !is BannerMessagePreference) {
+            return false
+        }
+
+        if (childPreferences.size >= MAX_CHILDREN) {
+            return false
+        }
+
+        childPreferences.add(preference)
+        return super.addPreference(preference)
+    }
+
+    override fun removePreference(preference: Preference): Boolean {
+        if (preference !is BannerMessagePreference) {
+            return false
+        }
+        childPreferences.remove(preference)
+        return super.removePreference(preference)
+    }
+
+    override fun onBindViewHolder(holder: PreferenceViewHolder) {
+        super.onBindViewHolder(holder)
+        if (childPreferences.size >= MAX_CHILDREN - 1) {
+            if (expandPreference == null) {
+                expandPreference = NumberButtonPreference(context).apply {
+                    key = expandKey
+                    title = expandTitle
+                    count = childPreferences.size - 1
+                    btnContentDescription = expandContentDescription
+                    clickListener = View.OnClickListener {
+                        toggleExpansion()
+                    }
+                }
+                super.addPreference(expandPreference!!)
+            }
+            if (collapsePreference == null) {
+                collapsePreference = SectionButtonPreference(context)
+                    .apply {
+                    key = collapseKey
+                    title = collapseTitle
+                    icon = collapseIcon
+                    setOnClickListener {
+                        toggleExpansion()
+                    }
+                }
+                super.addPreference(collapsePreference!!)
+            }
+        }
+        updateExpandCollapsePreference()
+        updateChildrenVisibility()
+    }
+
+    private fun updateExpandCollapsePreference() {
+        expandPreference?.isVisible = !isExpanded
+        collapsePreference?.isVisible = isExpanded
+    }
+
+    private fun updateChildrenVisibility() {
+        for (i in 1 until childPreferences.size) {
+            val child = childPreferences[i]
+            child.isVisible = isExpanded
+        }
+    }
+
+    private fun toggleExpansion() {
+        isExpanded = !isExpanded
+        updateExpandCollapsePreference()
+        updateChildrenVisibility()
+    }
+
+    private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+        context.obtainStyledAttributes(
+            attrs,
+            R.styleable.BannerMessagePreferenceGroup, defStyleAttr, 0
+        ).apply {
+            expandKey = getString(R.styleable.BannerMessagePreferenceGroup_expandKey)
+            expandTitle = getString(R.styleable.BannerMessagePreferenceGroup_expandTitle)
+            collapseKey = getString(R.styleable.BannerMessagePreferenceGroup_collapseKey)
+            collapseTitle = getString(R.styleable.BannerMessagePreferenceGroup_collapseTitle)
+            collapseIcon = getDrawable(R.styleable.BannerMessagePreferenceGroup_collapseIcon)
+            recycle()
+        }
+    }
+
+    companion object {
+        private const val MAX_CHILDREN = 3
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index 08dd27f..a377f31 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -14,12 +14,15 @@
         "SettingsLintDefaults",
     ],
 
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     resource_dirs: ["res"],
 
     static_libs: [
-        "androidx.preference_preference",
         "SettingsLibSettingsTheme",
+        "androidx.preference_preference",
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml
new file mode 100644
index 0000000..0972b62
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_materialColorSurfaceBright"/>
+    <item android:color="@color/settingslib_materialColorSurfaceBright" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml
new file mode 100644
index 0000000..9bf5c43
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/settingslib_materialColorSurfaceBright" />
+    <corners android:radius="@dimen/settingslib_expressive_radius_extralarge2"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml
new file mode 100644
index 0000000..b993811
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/settingslib_materialColorSecondaryContainer" />
+    <corners android:radius="100dp"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml
new file mode 100644
index 0000000..fa13b41
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4">
+
+    <LinearLayout
+        android:id="@+id/settingslib_number_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingVertical="@dimen/settingslib_expressive_space_small1"
+        android:paddingHorizontal="@dimen/settingslib_expressive_space_small4"
+        android:background="@drawable/settingslib_number_button_background">
+        <TextView
+            android:id="@+id/settingslib_number_count"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/SettingsLibNumberButtonStyle.Number"/>
+        <TextView
+            android:id="@+id/settingslib_number_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/SettingsLibNumberButtonStyle.Title"/>
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml
new file mode 100644
index 0000000..e7fb572
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4">
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/settingslib_section_button"
+        style="@style/SettingsLibSectionButtonStyle.Expressive" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/styles.xml b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
index 3963732..5208e20 100644
--- a/packages/SettingsLib/ButtonPreference/res/values/styles.xml
+++ b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
@@ -30,4 +30,33 @@
         <item name="android:textColor">@color/settingslib_btn_colored_text_material</item>
         <item name="android:background">@drawable/settingslib_btn_colored_material</item>
     </style>
+
+    <style name="SettingsLibSectionButtonStyle.Expressive"
+        parent="@style/SettingsLibButtonStyle.Expressive.Filled.Large">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="backgroundTint">@color/settingslib_section_button_background</item>
+        <item name="iconTint">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="SettingsLibNumberButtonStyle.Number"
+        parent="">
+        <item name="android:minWidth">@dimen/settingslib_expressive_space_small4</item>
+        <item name="android:minHeight">@dimen/settingslib_expressive_space_small4</item>
+        <item name="android:gravity">center</item>
+        <item name="android:background">@drawable/settingslib_number_count_background</item>
+        <item name="android:paddingStart">@dimen/settingslib_expressive_radius_extrasmall2</item>
+        <item name="android:paddingEnd">@dimen/settingslib_expressive_radius_extrasmall2</item>
+        <item name="android:layout_marginEnd">@dimen/settingslib_expressive_space_extrasmall4</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleMedium</item>
+        <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
+        <item name="android:importantForAccessibility">no</item>
+    </style>
+
+    <style name="SettingsLibNumberButtonStyle.Title"
+        parent="">
+        <item name="android:gravity">center</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item>
+        <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+        <item name="android:importantForAccessibility">no</item>
+    </style>
 </resources>
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt
new file mode 100644
index 0000000..a1772d5
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.settingslib.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.TextView
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+
+import com.android.settingslib.widget.preference.button.R
+
+class NumberButtonPreference @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
+
+    var clickListener: View.OnClickListener? = null
+
+    var count: Int = 0
+        set(value) {
+            field = value
+            notifyChanged()
+        }
+
+    var btnContentDescription: Int = 0
+        set(value) {
+            field = value
+            notifyChanged()
+        }
+
+    init {
+        isPersistent = false // This preference doesn't store data
+        order = Int.MAX_VALUE
+        layoutResource = R.layout.settingslib_number_button
+    }
+
+    override fun onBindViewHolder(holder: PreferenceViewHolder) {
+        super.onBindViewHolder(holder)
+        holder.isDividerAllowedAbove = false
+        holder.isDividerAllowedBelow = false
+
+        holder.findViewById(R.id.settingslib_number_button)?.apply {
+            setOnClickListener(clickListener)
+            if (btnContentDescription != 0) {
+                setContentDescription(context.getString(btnContentDescription, count))
+            }
+        }
+        (holder.findViewById(R.id.settingslib_number_title) as? TextView)?.text = title
+
+        (holder.findViewById(R.id.settingslib_number_count) as? TextView)?.text = "$count"
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt
new file mode 100644
index 0000000..b374dea
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.settingslib.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.button.R
+import com.google.android.material.button.MaterialButton
+
+/**
+ * A Preference that displays a button with an optional icon.
+ */
+class SectionButtonPreference @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
+
+    private var clickListener: ((View) -> Unit)? = null
+        set(value) {
+            field = value
+            notifyChanged()
+        }
+    private var button: MaterialButton? = null
+    init {
+        isPersistent = false // This preference doesn't store data
+        order = Int.MAX_VALUE
+        layoutResource = R.layout.settingslib_section_button
+    }
+
+    override fun onBindViewHolder(holder: PreferenceViewHolder) {
+        super.onBindViewHolder(holder)
+        holder.isDividerAllowedAbove = false
+        holder.isDividerAllowedBelow = false
+
+        button = holder.findViewById(R.id.settingslib_section_button) as? MaterialButton
+        button?.apply{
+            text = title
+            isFocusable = isSelectable
+            isClickable = isSelectable
+            setOnClickListener { view -> clickListener?.let { it(view) } }
+        }
+        button?.isEnabled = isEnabled
+        button?.icon = icon
+    }
+
+    /**
+     * Set a listener for button click
+     */
+    fun setOnClickListener(listener: (View) -> Unit) {
+        clickListener = listener
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index 8b29b00..ce66a36 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -40,7 +40,6 @@
 import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
 import com.android.settingslib.graph.proto.PreferenceScreenProto
 import com.android.settingslib.graph.proto.TextProto
-import com.android.settingslib.metadata.FloatPersistentPreference
 import com.android.settingslib.metadata.PersistentPreference
 import com.android.settingslib.metadata.PreferenceAvailabilityProvider
 import com.android.settingslib.metadata.PreferenceHierarchy
@@ -410,13 +409,13 @@
             value = preferenceValueProto {
                 when (metadata) {
                     is RangeValue -> storage.getInt(metadata.key)?.let { intValue = it }
-                    is FloatPersistentPreference ->
-                        storage.getFloat(metadata.key)?.let { floatValue = it }
                     else -> {}
                 }
                 when (metadata.valueType) {
                     Boolean::class.javaObjectType ->
                         storage.getBoolean(metadata.key)?.let { booleanValue = it }
+                    Float::class.javaObjectType ->
+                        storage.getFloat(metadata.key)?.let { floatValue = it }
                 }
             }
         }
@@ -428,12 +427,12 @@
                             max = metadata.getMaxValue(context)
                             step = metadata.getIncrementStep(context)
                         }
-                    is FloatPersistentPreference -> floatType = true
                     else -> {}
                 }
                 if (metadata is PersistentPreference<*>) {
                     when (metadata.valueType) {
                         Boolean::class.javaObjectType -> booleanType = true
+                        Float::class.javaObjectType -> floatType = true
                     }
                 }
             }
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
index 4cc6581..be705b5 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
@@ -76,7 +76,7 @@
 }
 
 /** Preference interface that has a value persisted in datastore. */
-interface PersistentPreference<T> {
+interface PersistentPreference<T> : PreferenceMetadata {
 
     /**
      * The value type the preference is associated with.
@@ -93,7 +93,7 @@
      * [PreferenceScreenRegistry.getKeyValueStore].
      */
     fun storage(context: Context): KeyValueStore =
-        PreferenceScreenRegistry.getKeyValueStore(context, this as PreferenceMetadata)!!
+        PreferenceScreenRegistry.getKeyValueStore(context, this)!!
 
     /** Returns the required permissions to read preference value. */
     fun getReadPermissions(context: Context): Permissions? = null
@@ -111,7 +111,7 @@
             context,
             callingPid,
             callingUid,
-            this as PreferenceMetadata,
+            this,
         )
 
     /** Returns the required permissions to write preference value. */
@@ -136,7 +136,7 @@
             value,
             callingPid,
             callingUid,
-            this as PreferenceMetadata,
+            this,
         )
 
     /** The sensitivity level of the preference. */
@@ -219,12 +219,3 @@
     override fun isValidValue(context: Context, index: Int) =
         index in getMinValue(context)..getMaxValue(context)
 }
-
-/** A persistent preference that has a boolean value. */
-interface BooleanPreference : PersistentPreference<Boolean> {
-    override val valueType: Class<Boolean>
-        get() = Boolean::class.javaObjectType
-}
-
-/** A persistent preference that has a float value. */
-interface FloatPersistentPreference : PersistentPreference<Float>
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
index b79a0c4..fecf3e5 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
@@ -18,8 +18,17 @@
 
 import androidx.annotation.StringRes
 
-/** Common base class for preferences that have two selectable states and save a boolean value. */
-interface TwoStatePreference : PreferenceMetadata, BooleanPreference
+/** A persistent preference that has a boolean value. */
+interface BooleanValuePreference : PersistentPreference<Boolean> {
+    override val valueType: Class<Boolean>
+        get() = Boolean::class.javaObjectType
+}
+
+/** A persistent preference that has a float value. */
+interface FloatValuePreference : PersistentPreference<Float> {
+    override val valueType: Class<Float>
+        get() = Float::class.javaObjectType
+}
 
 /** A preference that provides a two-state toggleable option. */
 open class SwitchPreference
@@ -28,9 +37,10 @@
     override val key: String,
     @StringRes override val title: Int = 0,
     @StringRes override val summary: Int = 0,
-) : TwoStatePreference
+) : BooleanValuePreference
 
 /** A preference that provides a two-state toggleable option that can be used as a main switch. */
 open class MainSwitchPreference
 @JvmOverloads
-constructor(override val key: String, @StringRes override val title: Int = 0) : TwoStatePreference
+constructor(override val key: String, @StringRes override val title: Int = 0) :
+    BooleanValuePreference
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index e4bc7b4..04df308 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,6 +3,7 @@
 chiujason@google.com
 cipson@google.com
 dsandler@android.com
+dswliu@google.com
 edgarwang@google.com
 evanlaird@google.com
 jiannan@google.com
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 6b7be91..c61c6a5 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.preference
 
 import android.content.Context
+import androidx.annotation.CallSuper
 import androidx.preference.DialogPreference
 import androidx.preference.ListPreference
 import androidx.preference.Preference
@@ -59,6 +60,7 @@
      * @param preference preference widget created by [createWidget]
      * @param metadata metadata to apply
      */
+    @CallSuper
     fun bind(preference: Preference, metadata: PreferenceMetadata) {
         metadata.apply {
             preference.key = key
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
index bd5d17c..65fbe2b 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -45,7 +45,7 @@
                     context.getString(screenTitle)
                 } else {
                     screenMetadata.getScreenTitle(context)
-                        ?: (this as? PreferenceTitleProvider)?.getTitle(context)
+                        ?: (screenMetadata as? PreferenceTitleProvider)?.getTitle(context)
                 }
         }
     }
@@ -66,7 +66,7 @@
 }
 
 /** A boolean value type preference associated with the abstract [TwoStatePreference]. */
-interface TwoStatePreferenceBinding : PreferenceBinding {
+interface BooleanValuePreferenceBinding : PreferenceBinding {
 
     override fun bind(preference: Preference, metadata: PreferenceMetadata) {
         super.bind(preference, metadata)
@@ -78,7 +78,7 @@
 }
 
 /** A boolean value type preference associated with [SwitchPreferenceCompat]. */
-interface SwitchPreferenceBinding : TwoStatePreferenceBinding {
+interface SwitchPreferenceBinding : BooleanValuePreferenceBinding {
 
     override fun createWidget(context: Context): Preference = SwitchPreferenceCompat(context)
 
@@ -88,7 +88,7 @@
 }
 
 /** A boolean value type preference associated with [MainSwitchPreference]. */
-interface MainSwitchPreferenceBinding : TwoStatePreferenceBinding {
+interface MainSwitchPreferenceBinding : BooleanValuePreferenceBinding {
 
     override fun createWidget(context: Context): Preference = MainSwitchPreference(context)
 
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
index e237a6a..ffe181d 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
@@ -21,6 +21,7 @@
 import android.os.Bundle
 import android.util.Log
 import androidx.annotation.XmlRes
+import androidx.lifecycle.Lifecycle
 import androidx.preference.PreferenceScreen
 import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
 import com.android.settingslib.metadata.PreferenceScreenBindingKeyProvider
@@ -38,6 +39,11 @@
         preferenceScreen = createPreferenceScreen()
     }
 
+    override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
+        super.setPreferenceScreen(preferenceScreen)
+        updateActivityTitle()
+    }
+
     fun createPreferenceScreen(): PreferenceScreen? =
         createPreferenceScreen(PreferenceScreenFactory(this))
 
@@ -102,9 +108,19 @@
 
     override fun onResume() {
         super.onResume()
+        // Even when activity has several fragments with preference screen, this will keep activity
+        // title in sync when fragment manager pops back stack.
+        updateActivityTitle()
         preferenceScreenBindingHelper?.onResume()
     }
 
+    internal fun updateActivityTitle() {
+        if (!lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) return
+        val activity = activity ?: return
+        val title = preferenceScreen?.title ?: return
+        if (activity.title != title) activity.title = title
+    }
+
     override fun onPause() {
         preferenceScreenBindingHelper?.onPause()
         super.onPause()
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 7492c2d..4a6a589 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -52,7 +52,7 @@
  */
 class PreferenceScreenBindingHelper(
     context: Context,
-    fragment: PreferenceFragment,
+    private val fragment: PreferenceFragment,
     private val preferenceBindingFactory: PreferenceBindingFactory,
     private val preferenceScreen: PreferenceScreen,
     private val preferenceHierarchy: PreferenceHierarchy,
@@ -156,7 +156,9 @@
 
         // bind preference to update UI
         preferenceScreen.findPreference<Preference>(key)?.let {
-            preferences[key]?.let { node -> preferenceBindingFactory.bind(it, node) }
+            val node = preferences[key] ?: return@let
+            preferenceBindingFactory.bind(it, node)
+            if (it == preferenceScreen) fragment.updateActivityTitle()
         }
 
         // check reason to avoid potential infinite loop
diff --git a/packages/SettingsLib/SettingsTheme/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp
index 1661dfb..e5c009d 100644
--- a/packages/SettingsLib/SettingsTheme/Android.bp
+++ b/packages/SettingsLib/SettingsTheme/Android.bp
@@ -16,6 +16,7 @@
     ],
     resource_dirs: ["res"],
     static_libs: [
+        "aconfig_settingstheme_exported_flags_java_lib",
         "androidx.preference_preference",
         "com.google.android.material_material",
     ],
@@ -23,12 +24,12 @@
     min_sdk_version: "21",
     apex_available: [
         "//apex_available:platform",
+        "com.android.adservices",
         "com.android.cellbroadcast",
         "com.android.devicelock",
         "com.android.extservices",
-        "com.android.permission",
-        "com.android.adservices",
         "com.android.healthfitness",
         "com.android.mediaprovider",
+        "com.android.permission",
     ],
 }
diff --git a/packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig b/packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig
new file mode 100644
index 0000000..83e732b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/aconfig/settingstheme.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.settingslib.widget.theme.flags"
+container: "system"
+
+flag {
+    name: "is_expressive_design_enabled"
+    namespace: "android_settings"
+    description: "enable expressive design in Settings"
+    bug: "386013400"
+    is_exported: true
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_chevron.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_chevron.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_collapse.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_collapse.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml
new file mode 100644
index 0000000..3ba85a2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml
@@ -0,0 +1,36 @@
+<!--
+  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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape
+            android:shape="oval">
+            <size android:width="28dp" android:height="28dp"/>
+            <solid android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+        </shape>
+    </item>
+    <item android:gravity="center">
+        <vector
+            android:width="16dp"
+            android:height="16dp"
+            android:viewportWidth="24"
+            android:viewportHeight="24">
+            <path
+                android:fillColor="@color/settingslib_materialColorOnSurface"
+                android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12 19,6.41z"/>
+        </vector>
+    </item>
+</layer-list>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_expand.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_expand.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml
new file mode 100644
index 0000000..aa4155b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <vector
+            android:width="20dp"
+            android:height="20dp"
+            android:viewportWidth="20"
+            android:viewportHeight="20">
+            <path
+                android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+                android:fillColor="@color/settingslib_colorBackgroundLevel_high"/>
+        </vector>
+    </item>
+    <item android:gravity="center">
+        <vector
+            android:width="4dp"
+            android:height="12dp"
+            android:viewportWidth="4"
+            android:viewportHeight="12">
+            <path
+                android:pathData="M0.894,8.081V0.919H3.106V8.081H0.894ZM0.894,11.081V8.869H3.106V11.081H0.894Z"
+                android:fillColor="@color/settingslib_colorContentLevel_high"/>
+        </vector>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml
new file mode 100644
index 0000000..9caa095
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <vector
+            android:width="20dp"
+            android:height="20dp"
+            android:viewportWidth="20"
+            android:viewportHeight="20">
+            <path
+                android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+                android:fillColor="@color/settingslib_colorBackgroundLevel_low"/>
+        </vector>
+    </item>
+    <item android:gravity="center">
+        <vector
+            android:width="10dp"
+            android:height="9dp"
+            android:viewportWidth="10"
+            android:viewportHeight="9">
+            <path
+                android:pathData="M3.5,8.975L0.069,5.544L1.644,3.969L3.5,5.825L8.356,0.969L9.931,2.544L3.5,8.975Z"
+                android:fillColor="@color/settingslib_colorContentLevel_low"/>
+        </vector>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml
new file mode 100644
index 0000000..cdcb982
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <vector
+            android:width="20dp"
+            android:height="20dp"
+            android:viewportWidth="20"
+            android:viewportHeight="20">
+            <path
+                android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+                android:fillColor="@color/settingslib_colorBackgroundLevel_medium"/>
+        </vector>
+    </item>
+    <item android:gravity="center">
+        <vector
+            android:width="4dp"
+            android:height="12dp"
+            android:viewportWidth="4"
+            android:viewportHeight="12">
+            <path
+                android:pathData="M0.894,8.081V0.919H3.106V8.081H0.894ZM0.894,11.081V8.869H3.106V11.081H0.894Z"
+                android:fillColor="@color/settingslib_colorContentLevel_medium"/>
+        </vector>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml
new file mode 100644
index 0000000..448d596
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <vector
+            android:width="20dp"
+            android:height="20dp"
+            android:viewportWidth="20"
+            android:viewportHeight="20">
+            <path
+                android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+                android:fillColor="@color/settingslib_materialColorOnSurface"/>
+        </vector>
+    </item>
+    <item android:gravity="center">
+        <vector
+            android:width="14dp"
+            android:height="4dp"
+            android:viewportWidth="14"
+            android:viewportHeight="4">
+            <path
+                android:pathData="M0.962,3.106V0.894H13.038V3.106H0.962Z"
+                android:fillColor="@color/settingslib_materialColorSurface"/>
+        </vector>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.xml
new file mode 100644
index 0000000..c387305
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="@color/settingslib_materialColorOnSurface"
+        android:pathData="M480,432L296,616L240,560L480,320L720,560L664,616L480,432Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
index 511e2bb..4ef747a 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
@@ -19,7 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:minHeight="72dp"
     android:gravity="center_vertical"
     android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
similarity index 97%
rename from packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
rename to packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
index 2776544..7d7bec14 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
@@ -48,6 +48,7 @@
         android:layout_height="wrap_content"
         app:layout_constraintTop_toBottomOf="@android:id/title"
         app:layout_constraintStart_toStartOf="parent"
+        android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
         android:textAlignment="viewStart"
         android:clickable="true"
         android:visibility="gone"
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
index e57fe4f..d677bba 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
@@ -50,4 +50,9 @@
     <color name="settingslib_materialColorSurfaceContainerLow">#0E0E0E</color>
     <color name="settingslib_materialColorSurfaceContainerHigh">#2A2A2A</color>
     <color name="settingslib_materialColorSurfaceContainerHighest">#343434</color>
+
+    <color name="settingslib_colorBackgroundLevel_high">@color/m3_ref_palette_red60</color>
+    <color name="settingslib_colorContentLevel_high">@color/m3_ref_palette_red10</color>
+    <color name="settingslib_colorBackgroundLevel_low">@color/m3_ref_palette_green70</color>
+    <color name="settingslib_colorContentLevel_low">@color/m3_ref_palette_green10</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
index b5f22b7..ec67d06 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
@@ -104,6 +104,11 @@
         <item name="android:layout_width">match_parent</item>
     </style>
 
+    <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive"
+        parent="@style/TextAppearance.SettingsLib.LabelLarge">
+        <item name="android:textColor">?android:attr/colorAccent</item>
+    </style>
+
     <style name="SettingslibTextButtonStyle.Expressive"
         parent="@style/Widget.Material3Expressive.Button.TextButton.Icon">
         <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
index 1a08568..3af88c4 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
@@ -70,11 +70,6 @@
         <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
     </style>
 
-    <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive"
-           parent="@style/TextAppearance.SettingsLib.LabelLarge">
-        <item name="android:textColor">?android:attr/colorAccent</item>
-    </style>
-
     <style name="SettingsLibStatusBannerCardStyle">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/colors.xml b/packages/SettingsLib/SettingsTheme/res/values/colors.xml
index c5c613b..e8ab99e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/colors.xml
@@ -76,4 +76,11 @@
     <color name="settingslib_materialColorSurfaceContainerLowest">#FFFFFF</color>
     <color name="settingslib_materialColorSurfaceContainerHigh">#E8E8E8</color>
     <color name="settingslib_materialColorSurfaceContainerHighest">#E3E3E3</color>
+
+    <color name="settingslib_colorBackgroundLevel_high">@color/m3_ref_palette_red50</color>
+    <color name="settingslib_colorContentLevel_high">@color/m3_ref_palette_red100</color>
+    <color name="settingslib_colorBackgroundLevel_medium">@color/m3_ref_palette_yellow80</color>
+    <color name="settingslib_colorContentLevel_medium">@color/m3_ref_palette_yellow10</color>
+    <color name="settingslib_colorBackgroundLevel_low">@color/m3_ref_palette_green50</color>
+    <color name="settingslib_colorContentLevel_low">@color/m3_ref_palette_green100</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
index 6794cd0..1776d25 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.os.Build
+import com.android.settingslib.widget.theme.flags.Flags
 
 object SettingsThemeHelper {
     private const val IS_EXPRESSIVE_DESIGN_ENABLED = "is_expressive_design_enabled"
@@ -56,7 +57,8 @@
         expressiveThemeState =
             if (
                 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) &&
-                        getPropBoolean(context, IS_EXPRESSIVE_DESIGN_ENABLED, false)
+                        (getPropBoolean(context, IS_EXPRESSIVE_DESIGN_ENABLED, false) ||
+                                Flags.isExpressiveDesignEnabled())
             ) {
                 ExpressiveThemeState.ENABLED
             } else {
diff --git a/packages/SettingsLib/Spa/.gitignore b/packages/SettingsLib/Spa/.gitignore
index b2ed268..5790fde 100644
--- a/packages/SettingsLib/Spa/.gitignore
+++ b/packages/SettingsLib/Spa/.gitignore
@@ -7,6 +7,7 @@
 /.idea/workspace.xml
 /.idea/navEditor.xml
 /.idea/assetWizardSettings.xml
+/.kotlin
 .DS_Store
 build
 /captures
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index cf695d0..5e72c43 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -28,7 +28,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.8.0-alpha06"
+    extra["jetpackComposeVersion"] = "1.8.0-alpha08"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 04ef96a..4113ad8 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,9 +15,9 @@
 #
 
 [versions]
-agp = "8.7.3"
+agp = "8.8.0"
 dexmaker-mockito = "2.28.3"
-jvm = "17"
+jvm = "21"
 kotlin = "2.0.21"
 truth = "1.4.4"
 
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index a0bbb0c..1396629 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -52,14 +52,14 @@
 dependencies {
     api(project(":SettingsLibColor"))
     api("androidx.appcompat:appcompat:1.7.0")
-    api("androidx.compose.material3:material3:1.4.0-alpha04")
+    api("androidx.compose.material3:material3:1.4.0-alpha05")
     api("androidx.compose.material:material-icons-extended")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.graphics:graphics-shapes-android:1.0.1")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.9.0-alpha03")
+    api("androidx.navigation:navigation-compose:2.9.0-alpha04")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.13.0-alpha08")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
index bdbe62c..8e59fd7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
@@ -20,9 +20,9 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
 import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuAnchorType
 import androidx.compose.material3.ExposedDropdownMenuBox
 import androidx.compose.material3.ExposedDropdownMenuDefaults
-import androidx.compose.material3.MenuAnchorType
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -66,7 +66,7 @@
         OutlinedTextField(
             // The `menuAnchor` modifier must be passed to the text field for correctness.
             modifier = Modifier
-                .menuAnchor(MenuAnchorType.PrimaryNotEditable)
+                .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
                 .fillMaxWidth(),
             value = text,
             onValueChange = { },
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
index 7dbd320..03cd243 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle.kts
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -36,7 +36,7 @@
 dependencies {
     api(project(":spa"))
 
-    api("androidx.arch.core:core-testing:2.2.0-alpha01")
+    api("androidx.arch.core:core-testing:2.2.0")
     api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-runtime-testing")
     api("org.mockito.kotlin:mockito-kotlin:2.2.11")
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
deleted file mode 100644
index 195d45f..0000000
--- a/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2022 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingBottom="16dp"
-    android:paddingTop="8dp"
-    android:background="?android:attr/selectableItemBackground"
-    android:clipToPadding="false">
-
-    <TextView
-        android:id="@android:id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="start"
-        android:textAlignment="viewStart"
-        android:clickable="false"
-        android:longClickable="false"
-        android:maxLines="10"
-        android:hyphenationFrequency="normalFast"
-        android:lineBreakWordStyle="phrase"
-        android:textAppearance="@style/TextAppearance.TopIntroText"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
similarity index 95%
rename from packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml
rename to packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
index fb13ef7..834814c 100644
--- a/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml
+++ b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2024 The Android Open Source Project
+  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.
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
deleted file mode 100644
index bee6bc7..0000000
--- a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2020 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.
-  -->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingBottom="16dp"
-    android:paddingTop="8dp"
-    android:background="?android:attr/selectableItemBackground"
-    android:clipToPadding="false">
-
-    <TextView
-        android:id="@android:id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="start"
-        android:textAlignment="viewStart"
-        android:clickable="false"
-        android:longClickable="false"
-        android:maxLines="10"
-        android:textAppearance="@style/TextAppearance.TopIntroText"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
index 4428480..08ba836 100644
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
@@ -17,20 +17,20 @@
 package com.android.settingslib.widget
 
 import android.content.Context
-import android.os.Build
 import android.text.TextUtils
 import android.util.AttributeSet
 import android.view.View
-import androidx.annotation.RequiresApi
 import androidx.preference.Preference
 import androidx.preference.PreferenceViewHolder
 import com.android.settingslib.widget.preference.topintro.R
 
-open class TopIntroPreference @JvmOverloads constructor(
+open class TopIntroPreference
+@JvmOverloads
+constructor(
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
-    defStyleRes: Int = 0
+    defStyleRes: Int = 0,
 ) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
 
     private var isCollapsable: Boolean = false
@@ -40,25 +40,18 @@
     private var learnMoreText: CharSequence? = null
 
     init {
-        if (SettingsThemeHelper.isExpressiveTheme(context)) {
-            layoutResource = R.layout.settingslib_expressive_top_intro
-            initAttributes(context, attrs, defStyleAttr)
-        } else {
-            layoutResource = R.layout.top_intro_preference
-        }
+        layoutResource = R.layout.settingslib_expressive_top_intro
+        initAttributes(context, attrs, defStyleAttr)
+
         isSelectable = false
     }
 
     private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
-        context.obtainStyledAttributes(
-            attrs,
-            COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0
-        ).apply {
+        context.obtainStyledAttributes(attrs, COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0).apply {
             isCollapsable = getBoolean(IS_COLLAPSABLE, false)
-            minLines = getInt(
-                MIN_LINES,
-                if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
-            ).coerceIn(1, DEFAULT_MAX_LINES)
+            minLines =
+                getInt(MIN_LINES, if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES)
+                    .coerceIn(1, DEFAULT_MAX_LINES)
             recycle()
         }
     }
@@ -68,10 +61,6 @@
         holder.isDividerAllowedAbove = false
         holder.isDividerAllowedBelow = false
 
-        if (!SettingsThemeHelper.isExpressiveTheme(context)) {
-            return
-        }
-
         (holder.findViewById(R.id.collapsable_text_view) as? CollapsableTextView)?.apply {
             setCollapsable(isCollapsable)
             setMinLines(minLines)
@@ -89,9 +78,9 @@
 
     /**
      * Sets whether the text view is collapsable.
+     *
      * @param collapsable True if the text view should be collapsable, false otherwise.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     fun setCollapsable(collapsable: Boolean) {
         isCollapsable = collapsable
         notifyChanged()
@@ -99,9 +88,9 @@
 
     /**
      * Sets the minimum number of lines to display when collapsed.
+     *
      * @param lines The minimum number of lines.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     fun setMinLines(lines: Int) {
         minLines = lines.coerceIn(1, DEFAULT_MAX_LINES)
         notifyChanged()
@@ -109,9 +98,9 @@
 
     /**
      * Sets the action when clicking on the hyperlink in the text.
+     *
      * @param listener The click listener for hyperlink.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     fun setHyperlinkListener(listener: View.OnClickListener) {
         if (hyperlinkListener != listener) {
             hyperlinkListener = listener
@@ -121,9 +110,9 @@
 
     /**
      * Sets the action when clicking on the learn more view.
+     *
      * @param listener The click listener for learn more.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     fun setLearnMoreAction(listener: View.OnClickListener) {
         if (learnMoreListener != listener) {
             learnMoreListener = listener
@@ -133,9 +122,9 @@
 
     /**
      * Sets the text of learn more view.
+     *
      * @param text The text of learn more.
      */
-    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
     fun setLearnMoreText(text: CharSequence) {
         if (!TextUtils.equals(learnMoreText, text)) {
             learnMoreText = text
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index 3c3de04..502eb6c 100644
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -41,4 +41,15 @@
     <string name="config_avatar_picker_class" translatable="false">
         com.android.avatarpicker.ui.AvatarPickerActivity
     </string>
+
+    <array name="config_override_carrier_5g_plus">
+        <item>@array/carrier_2032_5g_plus</item>
+    </array>
+
+    <integer-array name="carrier_2032_5g_plus">
+        <!-- carrier id: 2032 -->
+        <item>2032</item>
+        <!-- network type: "5G+" -->
+        <item>@string/data_connection_5g_plus_carrier_2032</item>
+    </integer-array>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 6cf9e83..3da2271 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1723,6 +1723,8 @@
 
     <!-- Content description of the data connection type 5G+. [CHAR LIMIT=NONE] -->
     <string name="data_connection_5g_plus" translatable="false">5G+</string>
+    <!-- Content description of the data connection type 5G+ for carrier 2032. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5g_plus_carrier_2032" translatable="false">5G+</string>
 
     <!-- Content description of the data connection type Carrier WiFi. [CHAR LIMIT=NONE] -->
     <string name="data_connection_carrier_wifi">W+</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt b/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt
new file mode 100644
index 0000000..a64e8cc
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/PreferenceBindings.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+@file:Suppress("ktlint:standard:filename") // remove once we have more bindings
+
+package com.android.settingslib
+
+import android.content.Context
+import androidx.preference.Preference
+import com.android.settingslib.metadata.PreferenceMetadata
+import com.android.settingslib.preference.PreferenceBinding
+
+/** Preference binding for [PrimarySwitchPreference]. */
+interface PrimarySwitchPreferenceBinding : PreferenceBinding {
+
+    override fun createWidget(context: Context): Preference = PrimarySwitchPreference(context)
+
+    override fun bind(preference: Preference, metadata: PreferenceMetadata) {
+        super.bind(preference, metadata)
+        (preference as PrimarySwitchPreference).apply {
+            isChecked = preferenceDataStore!!.getBoolean(key, false)
+            isSwitchEnabled = isEnabled
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index b7108c9..cf52eb3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -15,11 +15,14 @@
  */
 package com.android.settingslib.mobile;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.os.PersistableBundle;
 import android.telephony.Annotation;
 import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
@@ -196,9 +199,9 @@
         networkToIconLookup.put(toDisplayIconKey(
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA),
                 TelephonyIcons.NR_5G);
-        networkToIconLookup.put(toDisplayIconKey(
-                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
-                TelephonyIcons.NR_5G_PLUS);
+        networkToIconLookup.put(
+                toDisplayIconKey(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
+                config.mobileIconGroup5gPlus);
         networkToIconLookup.put(toIconKey(
                 TelephonyManager.NETWORK_TYPE_NR),
                 TelephonyIcons.NR_5G);
@@ -217,6 +220,7 @@
         public boolean hideLtePlus = false;
         public boolean hspaDataDistinguishable;
         public boolean alwaysShowDataRatIcon = false;
+        public MobileIconGroup mobileIconGroup5gPlus = TelephonyIcons.NR_5G_PLUS;
 
         /**
          * Reads the latest configs.
@@ -250,9 +254,54 @@
                 config.hideLtePlus = b.getBoolean(
                         CarrierConfigManager.KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL);
             }
+
+            SubscriptionManager subscriptionManager =
+                    context.getSystemService(SubscriptionManager.class);
+            if (subscriptionManager != null) {
+                SubscriptionInfo subInfo = subscriptionManager.getDefaultDataSubscriptionInfo();
+                if (subInfo != null) {
+                    readMobileIconGroup5gPlus(subInfo.getCarrierId(), res, config);
+                }
+            }
             return config;
         }
 
+        @SuppressLint("ResourceType")
+        private static void readMobileIconGroup5gPlus(int carrierId, Resources res, Config config) {
+            int networkTypeResId = 0;
+            TypedArray groupArray;
+            try {
+                groupArray = res.obtainTypedArray(R.array.config_override_carrier_5g_plus);
+            } catch (Resources.NotFoundException e) {
+                return;
+            }
+            for (int i = 0; i < groupArray.length() && networkTypeResId == 0; i++) {
+                int groupId = groupArray.getResourceId(i, 0);
+                if (groupId == 0) {
+                    continue;
+                }
+                TypedArray carrierArray;
+                try {
+                    carrierArray = res.obtainTypedArray(groupId);
+                } catch (Resources.NotFoundException e) {
+                    continue;
+                }
+                int groupCarrierId = carrierArray.getInt(0, 0);
+                if (groupCarrierId == carrierId) {
+                    networkTypeResId = carrierArray.getResourceId(1, 0);
+                }
+                carrierArray.recycle();
+            }
+            groupArray.recycle();
+
+            if (networkTypeResId != 0) {
+                config.mobileIconGroup5gPlus = new MobileIconGroup(
+                        TelephonyIcons.NR_5G_PLUS.name,
+                        networkTypeResId,
+                        TelephonyIcons.NR_5G_PLUS.dataType);
+            }
+        }
+
         /**
          * Returns true if this config and the other config are semantically equal.
          *
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenDurationDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/DndDurationDialogFactory.java
similarity index 96%
rename from packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenDurationDialog.java
rename to packages/SettingsLib/src/com/android/settingslib/notification/modes/DndDurationDialogFactory.java
index 98c3edb..c5fa0aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenDurationDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/DndDurationDialogFactory.java
@@ -44,7 +44,12 @@
 
 import java.util.Arrays;
 
-public class ZenDurationDialog {
+/**
+ * This dialog configures the default behavior that the user prefers when enabling DND.
+ * Not to be confused with {@link EnableDndDialogFactory}, which is the dialog that will be shown
+ * when the user enables DND if the "Ask every time" option was selected in this dialog.
+ */
+public class DndDurationDialogFactory {
     private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
     @VisibleForTesting
     protected static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
@@ -72,7 +77,7 @@
     @VisibleForTesting
     protected LayoutInflater mLayoutInflater;
 
-    public ZenDurationDialog(Context context) {
+    public DndDurationDialogFactory(Context context) {
         mContext = context;
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java
similarity index 95%
rename from packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
rename to packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java
index c48694c..f0e7fb8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogFactory.java
@@ -54,8 +54,15 @@
 import java.util.Locale;
 import java.util.Objects;
 
-public class EnableZenModeDialog {
-    private static final String TAG = "EnableZenModeDialog";
+/**
+ * When enabling DND, if the user has the setting to "Ask every time" for the duration, we show
+ * this dialog to allow the user to select for how long they want DND to be enabled this time.
+ * Not to be confused with {@link DndDurationDialogFactory}, which is the dialog that allows the
+ * user to configure the default behavior for enabling DND (and in turn may lead to this dialog
+ * being shown, since it contains the said "Ask every time" option).
+ */
+public class EnableDndDialogFactory {
+    private static final String TAG = "EnableDndDialogFactory";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
@@ -74,7 +81,7 @@
     private static final int MINUTES_MS = 60 * SECONDS_MS;
 
     @Nullable
-    private final ZenModeDialogMetricsLogger mMetricsLogger;
+    private final EnableDndDialogMetricsLogger mMetricsLogger;
 
     @VisibleForTesting
     protected Uri mForeverId;
@@ -101,17 +108,17 @@
     @VisibleForTesting
     protected LayoutInflater mLayoutInflater;
 
-    public EnableZenModeDialog(Context context) {
+    public EnableDndDialogFactory(Context context) {
         this(context, 0);
     }
 
-    public EnableZenModeDialog(Context context, int themeResId) {
+    public EnableDndDialogFactory(Context context, int themeResId) {
         this(context, themeResId, false /* cancelIsNeutral */,
-                new ZenModeDialogMetricsLogger(context));
+                new EnableDndDialogMetricsLogger(context));
     }
 
-    public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral,
-            ZenModeDialogMetricsLogger metricsLogger) {
+    public EnableDndDialogFactory(Context context, int themeResId, boolean cancelIsNeutral,
+            EnableDndDialogMetricsLogger metricsLogger) {
         mContext = context;
         mThemeResId = themeResId;
         mCancelIsNeutral = cancelIsNeutral;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDialogMetricsLogger.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogMetricsLogger.java
similarity index 93%
rename from packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDialogMetricsLogger.java
rename to packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogMetricsLogger.java
index 17695e3..552bf8d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDialogMetricsLogger.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableDndDialogMetricsLogger.java
@@ -22,12 +22,12 @@
 import com.android.internal.logging.nano.MetricsProto;
 
 /**
- * Logs ui events for {@link EnableZenModeDialog}.
+ * Logs ui events for {@link EnableDndDialogFactory}.
  */
-public class ZenModeDialogMetricsLogger {
+public class EnableDndDialogMetricsLogger {
     private final Context mContext;
 
-    public ZenModeDialogMetricsLogger(Context context) {
+    public EnableDndDialogMetricsLogger(Context context) {
         mContext = context;
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenDurationDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/DndDurationDialogFactoryTest.java
similarity index 65%
rename from packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenDurationDialogTest.java
rename to packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/DndDurationDialogFactoryTest.java
index 19845a0..0b05a4f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenDurationDialogTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/DndDurationDialogFactoryTest.java
@@ -16,6 +16,12 @@
 
 package com.android.settingslib.notification.modes;
 
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.ALWAYS_ASK_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.COUNTDOWN_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.FOREVER_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.MAX_BUCKET_MINUTES;
+import static com.android.settingslib.notification.modes.DndDurationDialogFactory.MIN_BUCKET_MINUTES;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
@@ -32,6 +38,8 @@
 
 import androidx.appcompat.app.AlertDialog;
 
+import com.android.settingslib.notification.modes.DndDurationDialogFactory.ConditionTag;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,8 +47,8 @@
 import org.robolectric.RuntimeEnvironment;
 
 @RunWith(RobolectricTestRunner.class)
-public class ZenDurationDialogTest {
-    private ZenDurationDialog mController;
+public class DndDurationDialogFactoryTest {
+    private DndDurationDialogFactory mController;
 
     private Context mContext;
     private LayoutInflater mLayoutInflater;
@@ -53,7 +61,7 @@
         mContentResolver = RuntimeEnvironment.application.getContentResolver();
         mLayoutInflater = LayoutInflater.from(mContext);
 
-        mController = spy(new ZenDurationDialog(mContext));
+        mController = spy(new DndDurationDialogFactory(mContext));
         mController.mLayoutInflater = mLayoutInflater;
         mController.getContentView();
         mBuilder = new AlertDialog.Builder(mContext);
@@ -65,12 +73,9 @@
                 Settings.Global.ZEN_DURATION_PROMPT);
         mController.setupDialog(mBuilder);
 
-        assertFalse(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb
-                .isChecked());
-        assertFalse(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb
-                .isChecked());
-        assertTrue(mController.getConditionTagAt(
-                ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+        assertTrue(mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
     }
 
     @Test
@@ -79,12 +84,9 @@
                 Settings.Secure.ZEN_DURATION_FOREVER);
         mController.setupDialog(mBuilder);
 
-        assertTrue(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb
-                .isChecked());
-        assertFalse(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb
-                .isChecked());
-        assertFalse(mController.getConditionTagAt(
-                ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
+        assertTrue(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
     }
 
     @Test
@@ -92,12 +94,9 @@
         Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_DURATION, 45);
         mController.setupDialog(mBuilder);
 
-        assertFalse(mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb
-                .isChecked());
-        assertTrue(mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb
-                .isChecked());
-        assertFalse(mController.getConditionTagAt(
-                ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+        assertTrue(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
     }
 
     @Test
@@ -106,8 +105,7 @@
                 Settings.Secure.ZEN_DURATION_FOREVER);
 
         mController.setupDialog(mBuilder);
-        mController.getConditionTagAt(ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.setChecked(
-                true);
+        mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX).rb.setChecked(true);
         mController.updateZenDuration(Settings.Secure.ZEN_DURATION_FOREVER);
 
         assertEquals(Settings.Secure.ZEN_DURATION_PROMPT, Settings.Secure.getInt(mContentResolver,
@@ -120,8 +118,7 @@
                 Settings.Secure.ZEN_DURATION_PROMPT);
 
         mController.setupDialog(mBuilder);
-        mController.getConditionTagAt(ZenDurationDialog.FOREVER_CONDITION_INDEX).rb.setChecked(
-                true);
+        mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
         mController.updateZenDuration(Settings.Secure.ZEN_DURATION_PROMPT);
 
         assertEquals(Settings.Secure.ZEN_DURATION_FOREVER, Settings.Secure.getInt(mContentResolver,
@@ -134,8 +131,7 @@
                 Settings.Secure.ZEN_DURATION_PROMPT);
 
         mController.setupDialog(mBuilder);
-        mController.getConditionTagAt(ZenDurationDialog.COUNTDOWN_CONDITION_INDEX).rb.setChecked(
-                true);
+        mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
         mController.updateZenDuration(Settings.Secure.ZEN_DURATION_PROMPT);
 
         // countdown defaults to 60 minutes:
@@ -152,59 +148,50 @@
         // click time button starts at 60 minutes
         // - 1 hour to MAX_BUCKET_MINUTES (12 hours), increments by 1 hour
         // - 0-60 minutes increments by 15 minutes
-        View view = mController.mZenRadioGroupContent.getChildAt(
-                ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
-        ZenDurationDialog.ConditionTag tag = mController.getConditionTagAt(
-                ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+        View view = mController.mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX);
+        ConditionTag tag = mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX);
 
         // test incrementing up:
-        mController.onClickTimeButton(view, tag, true, ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+        mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX);
         assertEquals(120, tag.countdownZenDuration); // goes from 1 hour to 2 hours
 
         // try clicking up 50 times - should max out at ZenDurationDialog.MAX_BUCKET_MINUTES
         for (int i = 0; i < 50; i++) {
-            mController.onClickTimeButton(view, tag, true,
-                    ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+            mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX);
         }
-        assertEquals(ZenDurationDialog.MAX_BUCKET_MINUTES, tag.countdownZenDuration);
+        assertEquals(MAX_BUCKET_MINUTES, tag.countdownZenDuration);
 
         // reset, test incrementing down:
         mController.mBucketIndex = -1; // reset current bucket index to reset countdownZenDuration
         tag.countdownZenDuration = 60; // back to default
-        mController.onClickTimeButton(view, tag, false,
-                ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+        mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX);
         assertEquals(45, tag.countdownZenDuration); // goes from 60 minutes to 45 minutes
 
         // try clicking down 50 times - should stop at MIN_BUCKET_MINUTES
         for (int i = 0; i < 50; i++) {
-            mController.onClickTimeButton(view, tag, false,
-                    ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+            mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX);
         }
-        assertEquals(ZenDurationDialog.MIN_BUCKET_MINUTES, tag.countdownZenDuration);
+        assertEquals(MIN_BUCKET_MINUTES, tag.countdownZenDuration);
 
         // reset countdownZenDuration to unbucketed number, should round change to nearest bucket
         mController.mBucketIndex = -1;
         tag.countdownZenDuration = 50;
-        mController.onClickTimeButton(view, tag, false,
-                ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+        mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX);
         assertEquals(45, tag.countdownZenDuration);
 
         mController.mBucketIndex = -1;
         tag.countdownZenDuration = 50;
-        mController.onClickTimeButton(view, tag, true,
-                ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+        mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX);
         assertEquals(60, tag.countdownZenDuration);
 
         mController.mBucketIndex = -1;
         tag.countdownZenDuration = 75;
-        mController.onClickTimeButton(view, tag, false,
-                ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+        mController.onClickTimeButton(view, tag, false, COUNTDOWN_CONDITION_INDEX);
         assertEquals(60, tag.countdownZenDuration);
 
         mController.mBucketIndex = -1;
         tag.countdownZenDuration = 75;
-        mController.onClickTimeButton(view, tag, true,
-                ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
+        mController.onClickTimeButton(view, tag, true, COUNTDOWN_CONDITION_INDEX);
         assertEquals(120, tag.countdownZenDuration);
     }
 
@@ -213,12 +200,9 @@
         Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_DURATION,
                 Settings.Secure.ZEN_DURATION_FOREVER);
         mController.setupDialog(mBuilder);
-        ZenDurationDialog.ConditionTag forever = mController.getConditionTagAt(
-                ZenDurationDialog.FOREVER_CONDITION_INDEX);
-        ZenDurationDialog.ConditionTag countdown = mController.getConditionTagAt(
-                ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
-        ZenDurationDialog.ConditionTag alwaysAsk = mController.getConditionTagAt(
-                ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX);
+        ConditionTag forever = mController.getConditionTagAt(FOREVER_CONDITION_INDEX);
+        ConditionTag countdown = mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX);
+        ConditionTag alwaysAsk = mController.getConditionTagAt(ALWAYS_ASK_CONDITION_INDEX);
 
         forever.rb.setChecked(true);
         assertThat(forever.line1.getStateDescription().toString()).isEqualTo("selected");
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableZenModeDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableDndDialogFactoryTest.java
similarity index 73%
rename from packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableZenModeDialogTest.java
rename to packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableDndDialogFactoryTest.java
index e397f97..fc9fd80 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableZenModeDialogTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/EnableDndDialogFactoryTest.java
@@ -16,6 +16,10 @@
 
 package com.android.settingslib.notification.modes;
 
+import static com.android.settingslib.notification.modes.EnableDndDialogFactory.COUNTDOWN_ALARM_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.EnableDndDialogFactory.COUNTDOWN_CONDITION_INDEX;
+import static com.android.settingslib.notification.modes.EnableDndDialogFactory.FOREVER_CONDITION_INDEX;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertFalse;
@@ -39,6 +43,8 @@
 import android.service.notification.Condition;
 import android.view.LayoutInflater;
 
+import com.android.settingslib.notification.modes.EnableDndDialogFactory.ConditionTag;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -48,8 +54,8 @@
 import org.robolectric.RuntimeEnvironment;
 
 @RunWith(RobolectricTestRunner.class)
-public class EnableZenModeDialogTest {
-    private EnableZenModeDialog mController;
+public class EnableDndDialogFactoryTest {
+    private EnableDndDialogFactory mController;
 
     @Mock
     private Context mContext;
@@ -74,7 +80,7 @@
         when(mFragment.getContext()).thenReturn(mShadowContext);
         mLayoutInflater = LayoutInflater.from(mShadowContext);
 
-        mController = spy(new EnableZenModeDialog(mContext));
+        mController = spy(new EnableDndDialogFactory(mContext));
         mController.mContext = mContext;
         mController.mLayoutInflater = mLayoutInflater;
         mController.mForeverId = Condition.newId(mContext).appendPath("forever").build();
@@ -101,36 +107,29 @@
         Uri countdown = Condition.newId(mContext).appendPath("countdown").build();
         mCountdownCondition = new Condition(countdown, "countdown", "", "", 0, 0, 0);
         mController.bind(mCountdownCondition,
-                mController.mZenRadioGroupContent.getChildAt(
-                        EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX),
-                EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX);
+                mController.mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
+                COUNTDOWN_CONDITION_INDEX);
         mController.bind(mAlarmCondition,
                 mController.mZenRadioGroupContent.getChildAt(
-                        EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX),
-                EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX);
+                        COUNTDOWN_ALARM_CONDITION_INDEX),
+                COUNTDOWN_ALARM_CONDITION_INDEX);
     }
 
     @Test
     public void testForeverChecked() {
         mController.bindConditions(mController.forever());
 
-        assertTrue(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
-                .isChecked());
-        assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
-                .isChecked());
-        assertFalse(mController.getConditionTagAt(
-                EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+        assertTrue(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
     }
 
     @Test
     public void testNoneChecked() {
         mController.bindConditions(null);
-        assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
-                .isChecked());
-        assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
-                .isChecked());
-        assertFalse(mController.getConditionTagAt(
-                EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
     }
 
     @Test
@@ -139,12 +138,9 @@
         doReturn(true).when(mController).isAlarm(mAlarmCondition);
 
         mController.bindConditions(mAlarmCondition);
-        assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
-                .isChecked());
-        assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
-                .isChecked());
-        assertTrue(mController.getConditionTagAt(
-                EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+        assertTrue(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
     }
 
     @Test
@@ -153,12 +149,9 @@
         doReturn(true).when(mController).isCountdown(mCountdownCondition);
 
         mController.bindConditions(mCountdownCondition);
-        assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
-                .isChecked());
-        assertTrue(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
-                .isChecked());
-        assertFalse(mController.getConditionTagAt(
-                EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(FOREVER_CONDITION_INDEX).rb.isChecked());
+        assertTrue(mController.getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.isChecked());
+        assertFalse(mController.getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
     }
 
     @Test
@@ -198,12 +191,12 @@
     @Test
     public void testAccessibility() {
         mController.bindConditions(null);
-        EnableZenModeDialog.ConditionTag forever = mController.getConditionTagAt(
-                ZenDurationDialog.FOREVER_CONDITION_INDEX);
-        EnableZenModeDialog.ConditionTag countdown = mController.getConditionTagAt(
-                ZenDurationDialog.COUNTDOWN_CONDITION_INDEX);
-        EnableZenModeDialog.ConditionTag alwaysAsk = mController.getConditionTagAt(
-                ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX);
+        ConditionTag forever = mController.getConditionTagAt(
+                DndDurationDialogFactory.FOREVER_CONDITION_INDEX);
+        ConditionTag countdown = mController.getConditionTagAt(
+                DndDurationDialogFactory.COUNTDOWN_CONDITION_INDEX);
+        ConditionTag alwaysAsk = mController.getConditionTagAt(
+                DndDurationDialogFactory.ALWAYS_ASK_CONDITION_INDEX);
 
         forever.rb.setChecked(true);
         assertThat(forever.line1.getStateDescription().toString()).isEqualTo("selected");
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 7aef657..d936a5c6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -92,6 +92,7 @@
         Settings.Secure.KEY_REPEAT_DELAY_MS,
         Settings.Secure.CAMERA_GESTURE_DISABLED,
         Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
+        Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT,
         Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
         Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
         Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
@@ -203,7 +204,6 @@
         Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT,
         Settings.Secure.PEOPLE_STRIP,
         Settings.Secure.MEDIA_CONTROLS_RESUME,
-        Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
         Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
         Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 1f56f10..cf0447f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -117,6 +117,7 @@
                 Settings.System.TOUCHPAD_TAP_TO_CLICK,
                 Settings.System.TOUCHPAD_TAP_DRAGGING,
                 Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE,
+                Settings.System.TOUCHPAD_ACCELERATION_ENABLED,
                 Settings.System.CAMERA_FLASH_NOTIFICATION,
                 Settings.System.SCREEN_FLASH_NOTIFICATION,
                 Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 43ceda7..919c3c4 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -142,6 +142,8 @@
         VALIDATORS.put(Secure.CAMERA_GESTURE_DISABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_LARGE_POINTER_ICON, BOOLEAN_VALIDATOR);
@@ -310,7 +312,6 @@
         VALIDATORS.put(Secure.TAP_GESTURE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Secure.MEDIA_CONTROLS_RECOMMENDATION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.MEDIA_CONTROLS_LOCK_SCREEN, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
                 new InclusiveIntegerRangeValidator(
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 4d98a11..4f649ed 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -234,6 +234,7 @@
         VALIDATORS.put(System.TOUCHPAD_TAP_DRAGGING, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.TOUCHPAD_RIGHT_CLICK_ZONE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.TOUCHPAD_SYSTEM_GESTURES, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.TOUCHPAD_ACCELERATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.LOCK_TO_APP_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 System.EGG_MODE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 1c6d681..9505977 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1718,6 +1718,9 @@
                 Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
                 SecureSettingsProto.Accessibility.AUTOCLICK_CURSOR_AREA_SIZE);
         dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT,
+                SecureSettingsProto.Accessibility.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT);
+        dumpSetting(s, p,
                 Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
                 SecureSettingsProto.Accessibility.AUTOCLICK_ENABLED);
         dumpSetting(s, p,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 4448000..a044738 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -998,6 +998,11 @@
     <!-- Permission required for CTS test - CtsContentProviderMultiUserTest -->
     <uses-permission android:name="android.permission.RESOLVE_COMPONENT_FOR_UID" />
 
+    <!-- Permissions required for CTS test - MediaQualityTest -->
+    <uses-permission android:name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE" />
+    <uses-permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE" />
+    <uses-permission android:name="android.permission.READ_COLOR_ZONES" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 67a9064..0a7d880 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -532,9 +532,13 @@
         "-Adagger.fastInit=enabled",
         "-Adagger.explicitBindingConflictsWithInject=ERROR",
         "-Adagger.strictMultibindingValidation=enabled",
+        "-Adagger.useBindingGraphFix=ENABLED",
         "-Aroom.schemaLocation=frameworks/base/packages/SystemUI/schemas",
     ],
-    kotlincflags: ["-Xjvm-default=all"],
+    kotlincflags: [
+        "-Xjvm-default=all",
+        "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
+    ],
 
     plugins: [
         "androidx.room_room-compiler-plugin",
@@ -712,6 +716,9 @@
     use_resource_processor: true,
     manifest: "tests/AndroidManifest-base.xml",
     resource_dirs: [],
+
+    kotlin_lang_version: "1.9",
+
     additional_manifests: ["tests/AndroidManifest.xml"],
     srcs: [
         "tests/src/**/*.kt",
@@ -758,7 +765,12 @@
         "-Xjvm-default=all",
         // TODO(b/352363800): Why do we need this?
         "-J-Xmx8192M",
+        "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
     ],
+    javacflags: [
+        "-Adagger.useBindingGraphFix=ENABLED",
+    ],
+
     aaptflags: [
         "--extra-packages",
         "com.android.systemui",
@@ -849,7 +861,6 @@
         "androidx.test.ext.truth",
     ],
 
-
     instrumentation_for: "SystemUIRobo-stub",
     java_resource_dirs: ["tests/robolectric/config"],
     plugins: [
@@ -886,7 +897,6 @@
         "androidx.test.ext.truth",
     ],
 
-
     instrumentation_for: "SystemUIRobo-stub",
     java_resource_dirs: ["tests/robolectric/config"],
     plugins: [
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 0726253..33e9919 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -40,6 +40,7 @@
 gallmann@google.com
 graciecheng@google.com
 gwasserman@google.com
+helencheuk@google.com
 hwwang@google.com
 hyunyoungs@google.com
 ikateryna@google.com
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 9531bc3..f753316 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -176,13 +176,6 @@
 }
 
 flag {
-    name: "notifications_dismiss_pruned_summaries"
-    namespace: "systemui"
-    description: "NotifCollection.dismissNotifications will now dismiss summaries that are pruned from the shade."
-    bug: "355967751"
-}
-
-flag {
    name: "notification_transparent_header_fix"
    namespace: "systemui"
    description: "fix the transparent group header issue for async header inflation."
@@ -1940,3 +1933,17 @@
         purpose: PURPOSE_BUGFIX
    }
 }
+
+flag {
+    name: "ui_rich_ongoing_force_expanded"
+    namespace: "systemui"
+    description: "Force promoted notifications to always be expanded"
+    bug: "380901479"
+}
+
+flag {
+    name: "aod_ui_rich_ongoing"
+    namespace: "systemui"
+    description: "Show a rich ongoing notification on the always-on display (depends on ui_rich_ongoing)"
+    bug: "369151941"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
index d992403..fc01c78 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
@@ -18,10 +18,10 @@
 
 import androidx.annotation.VisibleForTesting
 import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.foundation.OverscrollEffect
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
@@ -41,7 +41,7 @@
 class OffsetOverscrollEffect(
     orientation: Orientation,
     animationScope: CoroutineScope,
-    animationSpec: AnimationSpec<Float> = DefaultAnimationSpec,
+    animationSpec: AnimationSpec<Float>,
 ) : BaseContentOverscrollEffect(orientation, animationScope, animationSpec) {
     private var _node: DelegatableNode = newNode()
     override val node: DelegatableNode
@@ -71,13 +71,6 @@
     companion object {
         private val MaxDistance = 400.dp
 
-        internal val DefaultAnimationSpec =
-            spring(
-                stiffness = Spring.StiffnessLow,
-                dampingRatio = Spring.DampingRatioLowBouncy,
-                visibilityThreshold = 0.5f,
-            )
-
         @VisibleForTesting
         fun computeOffset(density: Density, overscrollDistance: Float): Int {
             val maxDistancePx = with(density) { MaxDistance.toPx() }
@@ -87,10 +80,11 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun rememberOffsetOverscrollEffect(
     orientation: Orientation,
-    animationSpec: AnimationSpec<Float> = OffsetOverscrollEffect.DefaultAnimationSpec,
+    animationSpec: AnimationSpec<Float> = MaterialTheme.motionScheme.defaultSpatialSpec(),
 ): OffsetOverscrollEffect {
     val animationScope = rememberCoroutineScope()
     return remember(orientation, animationScope, animationSpec) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index d43b596..456edaf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -89,9 +89,9 @@
 import com.android.compose.animation.Easings
 import com.android.compose.animation.scene.ContentScope
 import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.transitions
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
@@ -494,7 +494,7 @@
     val currentSceneKey =
         if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
 
-    val state = remember { MutableSceneTransitionLayoutState(currentSceneKey, SceneTransitions) }
+    val state = rememberMutableSceneTransitionLayoutState(currentSceneKey, SceneTransitions)
 
     // Update state whenever currentSceneKey has changed.
     LaunchedEffect(state, currentSceneKey) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 9b5ff7f..824f5a2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -43,6 +43,7 @@
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.observableTransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.transitions
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
@@ -165,13 +166,13 @@
         viewModel.communalBackground.collectAsStateWithLifecycle(
             initialValue = CommunalBackgroundType.ANIMATED
         )
-    val state: MutableSceneTransitionLayoutState = remember {
-        MutableSceneTransitionLayoutState(
+    val state: MutableSceneTransitionLayoutState =
+        rememberMutableSceneTransitionLayoutState(
             initialScene = currentSceneKey,
             canChangeScene = { _ -> viewModel.canChangeScene() },
             transitions = if (viewModel.v2FlagEnabled()) sceneTransitionsV2 else sceneTransitions,
         )
-    }
+
     val isUiBlurred by viewModel.isUiBlurred.collectAsStateWithLifecycle()
 
     val detector = remember { CommunalSwipeDetector() }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 70a74f0..3c0480d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1065,8 +1065,7 @@
                 ) {
                     Icon(
                         imageVector = Icons.Default.Add,
-                        contentDescription =
-                            stringResource(R.string.label_for_button_in_empty_state_cta),
+                        contentDescription = null,
                         modifier = Modifier.size(24.dp),
                     )
                     Spacer(Modifier.width(ButtonDefaults.IconSpacing))
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 79cf24b..410499a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -27,7 +27,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
@@ -36,7 +35,7 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ContentScope
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
@@ -82,9 +81,11 @@
                     WeatherClockScenes.splitShadeLargeClockScene
             }
 
-        val state = remember {
-            MutableSceneTransitionLayoutState(currentScene, ClockTransition.defaultClockTransitions)
-        }
+        val state =
+            rememberMutableSceneTransitionLayoutState(
+                currentScene,
+                ClockTransition.defaultClockTransitions,
+            )
 
         // Update state whenever currentSceneKey has changed.
         LaunchedEffect(state, currentScene) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index a6a6362..0ca7a6a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -36,7 +36,6 @@
 import androidx.compose.ui.platform.LocalView
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ContentKey
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
@@ -44,6 +43,7 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.observableTransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
 import com.android.systemui.lifecycle.rememberActivated
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.qs.ui.composable.QuickSettingsTheme
@@ -94,29 +94,27 @@
     val sceneJankMonitor =
         rememberActivated(traceName = "sceneJankMonitor") { sceneJankMonitorFactory.create() }
 
-    val state: MutableSceneTransitionLayoutState =
-        remember(view, sceneJankMonitor) {
-            MutableSceneTransitionLayoutState(
-                initialScene = initialSceneKey,
-                canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
-                transitions = sceneTransitions,
-                onTransitionStart = { transition ->
-                    sceneJankMonitor.onTransitionStart(
-                        view = view,
-                        from = transition.fromContent,
-                        to = transition.toContent,
-                        cuj = transition.cuj,
-                    )
-                },
-                onTransitionEnd = { transition ->
-                    sceneJankMonitor.onTransitionEnd(
-                        from = transition.fromContent,
-                        to = transition.toContent,
-                        cuj = transition.cuj,
-                    )
-                },
-            )
-        }
+    val state =
+        rememberMutableSceneTransitionLayoutState(
+            initialScene = initialSceneKey,
+            canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
+            transitions = sceneTransitions,
+            onTransitionStart = { transition ->
+                sceneJankMonitor.onTransitionStart(
+                    view = view,
+                    from = transition.fromContent,
+                    to = transition.toContent,
+                    cuj = transition.cuj,
+                )
+            },
+            onTransitionEnd = { transition ->
+                sceneJankMonitor.onTransitionEnd(
+                    from = transition.fromContent,
+                    to = transition.toContent,
+                    cuj = transition.cuj,
+                )
+            },
+        )
 
     DisposableEffect(state) {
         val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
@@ -179,6 +177,12 @@
                 }
             }
     ) {
+        SceneRevealScrim(
+            viewModel = viewModel.lightRevealScrim,
+            wallpaperViewModel = viewModel.wallpaperViewModel,
+            modifier = Modifier.fillMaxSize(),
+        )
+
         SceneTransitionLayout(
             state = state,
             modifier = modifier.fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index aa8b4ae..d7545cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -1,6 +1,5 @@
 package com.android.systemui.scene.ui.composable
 
-import androidx.compose.animation.core.spring
 import com.android.compose.animation.scene.TransitionKey
 import com.android.compose.animation.scene.transitions
 import com.android.internal.jank.Cuj
@@ -48,9 +47,6 @@
 val SceneContainerTransitions = transitions {
     interruptionHandler = SceneContainerInterruptionHandler
 
-    defaultMotionSpatialSpec =
-        spring(stiffness = 300f, dampingRatio = 0.8f, visibilityThreshold = 0.5f)
-
     // Scene transitions
 
     from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt
new file mode 100644
index 0000000..a1f89fcb
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.scene.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.wallpapers.ui.viewmodel.WallpaperViewModel
+
+@Composable
+fun SceneRevealScrim(
+    viewModel: LightRevealScrimViewModel,
+    wallpaperViewModel: WallpaperViewModel,
+    modifier: Modifier = Modifier,
+) {
+    AndroidView(
+        factory = { context ->
+            LightRevealScrim(context).apply {
+                LightRevealScrimViewBinder.bind(
+                    revealScrim = this,
+                    viewModel = viewModel,
+                    wallpaperViewModel = wallpaperViewModel,
+                )
+            }
+        },
+        modifier = modifier,
+    )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
index e30e7d3..b53c4e238 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.compose.animation.scene.UserActionDistance
@@ -30,11 +28,6 @@
 
 fun TransitionBuilder.goneToSplitShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
-        )
     distance = UserActionDistance { fromContent, _, _ ->
         val fromContentSize = checkNotNull(fromContent.targetSize())
         fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
index 1a243ca..6c8885e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.compose.animation.scene.UserActionDistance
@@ -29,11 +27,6 @@
 
 fun TransitionBuilder.lockscreenToSplitShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
-        )
     distance = UserActionDistance { fromContent, _, _ ->
         val fromContentSize = checkNotNull(fromContent.targetSize())
         fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
index a9af95b..9a7f99f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
@@ -16,22 +16,14 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.shade.ui.composable.Shade
 import kotlin.time.Duration.Companion.milliseconds
 
 fun TransitionBuilder.notificationsShadeToQuickSettingsShadeTransition(
     durationScale: Double = 1.0
 ) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
-        )
 }
 
 private val DefaultDuration = 300.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index ddea585..019639d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
@@ -26,16 +24,10 @@
 import com.android.systemui.notifications.ui.composable.NotificationsShade
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.composable.Shade
 import kotlin.time.Duration.Companion.milliseconds
 
 fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
-        )
     // Ensure the clock isn't clipped by the shade outline during the transition from lockscreen.
     sharedElement(
         ClockElementKeys.smallClockElementKey,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index e477a41..faccf14 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -16,23 +16,15 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.compose.animation.scene.UserActionDistance
 import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.composable.Shade
 import kotlin.time.Duration.Companion.milliseconds
 
 fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
-        )
     distance = UserActionDistance { fromContent, _, _ ->
         val fromContentSize = checkNotNull(fromContent.targetSize())
         fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
index 4db4934..2ed296b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
@@ -32,11 +30,6 @@
 
 fun TransitionBuilder.toShadeTransition(durationScale: Double = 1.0) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
-        )
     distance = UserActionDistance { _, _, _ ->
         Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 3295dde..bcd4d92 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -28,6 +28,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.Expandable
@@ -54,7 +55,7 @@
             VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN,
             0,
             null,
-            viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive }
+            viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive },
         )
         val gravity = horizontalGravity or Gravity.BOTTOM
         volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) })
@@ -95,14 +96,14 @@
                     icon = { Icon(icon = buttonViewModel.button.icon) },
                     label = {
                         Text(
-                            modifier = Modifier.basicMarquee(),
                             text = label,
                             style = MaterialTheme.typography.labelMedium,
                             color = LocalContentColor.current,
                             textAlign = TextAlign.Center,
-                            maxLines = 2
+                            maxLines = 1,
+                            overflow = TextOverflow.Ellipsis,
                         )
-                    }
+                    },
                 )
             }
         }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
index d876606..b04d89d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
@@ -19,6 +19,7 @@
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.SpringSpec
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import com.android.compose.animation.scene.content.state.TransitionState
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
@@ -32,7 +33,10 @@
 ): Job {
     oneOffAnimation.onRun = {
         // Animate the progress to its target value.
-        val animationSpec = transition.transformationSpec.progressSpec
+        @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+        val animationSpec =
+            transition.transformationSpec.progressSpec
+                ?: layoutState.motionScheme.defaultSpatialSpec()
         val visibilityThreshold =
             (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
         val replacedTransition = transition.replacedTransition
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index a1117e1..431a376 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -159,6 +159,9 @@
     /** The state of the [SceneTransitionLayout] in which this content is contained. */
     val layoutState: SceneTransitionLayoutState
 
+    /** The [LookaheadScope] used by the [SceneTransitionLayout]. */
+    val lookaheadScope: LookaheadScope
+
     /**
      * Tag an element identified by [key].
      *
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 8153586..5b275a5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -14,14 +14,22 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
+
 package com.android.compose.animation.scene
 
 import android.util.Log
 import androidx.annotation.VisibleForTesting
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MotionScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastAny
@@ -226,6 +234,7 @@
  */
 fun MutableSceneTransitionLayoutState(
     initialScene: SceneKey,
+    motionScheme: MotionScheme,
     transitions: SceneTransitions = SceneTransitions.Empty,
     initialOverlays: Set<OverlayKey> = emptySet(),
     canChangeScene: (SceneKey) -> Boolean = { true },
@@ -237,6 +246,7 @@
 ): MutableSceneTransitionLayoutState {
     return MutableSceneTransitionLayoutStateImpl(
         initialScene,
+        motionScheme,
         transitions,
         initialOverlays,
         canChangeScene,
@@ -248,19 +258,62 @@
     )
 }
 
+@Composable
+fun rememberMutableSceneTransitionLayoutState(
+    initialScene: SceneKey,
+    transitions: SceneTransitions = SceneTransitions.Empty,
+    initialOverlays: Set<OverlayKey> = emptySet(),
+    canChangeScene: (SceneKey) -> Boolean = { true },
+    canShowOverlay: (OverlayKey) -> Boolean = { true },
+    canHideOverlay: (OverlayKey) -> Boolean = { true },
+    canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
+    onTransitionStart: (TransitionState.Transition) -> Unit = {},
+    onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+): MutableSceneTransitionLayoutState {
+    val motionScheme = MaterialTheme.motionScheme
+    val layoutState = remember {
+        MutableSceneTransitionLayoutStateImpl(
+            initialScene = initialScene,
+            motionScheme = motionScheme,
+            transitions = transitions,
+            initialOverlays = initialOverlays,
+            canChangeScene = canChangeScene,
+            canShowOverlay = canShowOverlay,
+            canHideOverlay = canHideOverlay,
+            canReplaceOverlay = canReplaceOverlay,
+            onTransitionStart = onTransitionStart,
+            onTransitionEnd = onTransitionEnd,
+        )
+    }
+
+    SideEffect {
+        layoutState.transitions = transitions
+        layoutState.motionScheme = motionScheme
+        layoutState.canChangeScene = canChangeScene
+        layoutState.canShowOverlay = canShowOverlay
+        layoutState.canHideOverlay = canHideOverlay
+        layoutState.canReplaceOverlay = canReplaceOverlay
+        layoutState.onTransitionStart = onTransitionStart
+        layoutState.onTransitionEnd = onTransitionEnd
+    }
+    return layoutState
+}
+
 /** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 internal class MutableSceneTransitionLayoutStateImpl(
     initialScene: SceneKey,
+    internal var motionScheme: MotionScheme,
     override var transitions: SceneTransitions = transitions {},
     initialOverlays: Set<OverlayKey> = emptySet(),
-    internal val canChangeScene: (SceneKey) -> Boolean = { true },
-    internal val canShowOverlay: (OverlayKey) -> Boolean = { true },
-    internal val canHideOverlay: (OverlayKey) -> Boolean = { true },
-    internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
+    internal var canChangeScene: (SceneKey) -> Boolean = { true },
+    internal var canShowOverlay: (OverlayKey) -> Boolean = { true },
+    internal var canHideOverlay: (OverlayKey) -> Boolean = { true },
+    internal var canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
         true
     },
-    private val onTransitionStart: (TransitionState.Transition) -> Unit = {},
-    private val onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+    internal var onTransitionStart: (TransitionState.Transition) -> Unit = {},
+    internal var onTransitionEnd: (TransitionState.Transition) -> Unit = {},
 ) : MutableSceneTransitionLayoutState {
     private val creationThread: Thread = Thread.currentThread()
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index d50304d4..52c9ddc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -17,10 +17,7 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.snap
-import androidx.compose.animation.core.spring
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastForEach
@@ -34,7 +31,6 @@
 /** The transitions configuration of a [SceneTransitionLayout]. */
 class SceneTransitions
 internal constructor(
-    internal val defaultMotionSpatialSpec: SpringSpec<Float>,
     internal val transitionSpecs: List<TransitionSpecImpl>,
     internal val interruptionHandler: InterruptionHandler,
 ) {
@@ -123,16 +119,8 @@
         )
 
     companion object {
-        internal val DefaultSwipeSpec =
-            spring(
-                stiffness = Spring.StiffnessMediumLow,
-                dampingRatio = Spring.DampingRatioLowBouncy,
-                visibilityThreshold = OffsetVisibilityThreshold,
-            )
-
         val Empty =
             SceneTransitions(
-                defaultMotionSpatialSpec = DefaultSwipeSpec,
                 transitionSpecs = emptyList(),
                 interruptionHandler = DefaultInterruptionHandler,
             )
@@ -188,15 +176,7 @@
      * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
      * the transition is triggered (i.e. it is not gesture-based).
      */
-    val progressSpec: AnimationSpec<Float>
-
-    /**
-     * The [SpringSpec] used to animate the associated transition progress when the transition was
-     * started by a swipe and is now animating back to a scene because the user lifted their finger.
-     *
-     * If `null`, then the [SceneTransitions.defaultMotionSpatialSpec] will be used.
-     */
-    val motionSpatialSpec: AnimationSpec<Float>?
+    val progressSpec: AnimationSpec<Float>?
 
     /**
      * The distance it takes for this transition to animate from 0% to 100% when it is driven by a
@@ -213,7 +193,6 @@
         internal val Empty =
             TransformationSpecImpl(
                 progressSpec = snap(),
-                motionSpatialSpec = null,
                 distance = null,
                 transformationMatchers = emptyList(),
             )
@@ -246,7 +225,6 @@
                 val reverse = transformationSpec.invoke(transition)
                 TransformationSpecImpl(
                     progressSpec = reverse.progressSpec,
-                    motionSpatialSpec = reverse.motionSpatialSpec,
                     distance = reverse.distance,
                     transformationMatchers =
                         reverse.transformationMatchers.map {
@@ -275,8 +253,7 @@
  * [ElementTransformations].
  */
 internal class TransformationSpecImpl(
-    override val progressSpec: AnimationSpec<Float>,
-    override val motionSpatialSpec: SpringSpec<Float>?,
+    override val progressSpec: AnimationSpec<Float>?,
     override val distance: UserActionDistance?,
     override val transformationMatchers: List<TransformationMatcher>,
 ) : TransformationSpec {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 4137f5f..3bd37ad 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
+
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -364,10 +367,7 @@
 
         check(isAnimatingOffset())
 
-        val motionSpatialSpec =
-            spec
-                ?: contentTransition.transformationSpec.motionSpatialSpec
-                ?: layoutState.transitions.defaultMotionSpatialSpec
+        val motionSpatialSpec = spec ?: layoutState.motionScheme.defaultSpatialSpec()
 
         val velocityConsumed = CompletableDeferred<Float>()
         offsetAnimationRunnable.complete {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 776d553..a29c1bb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -19,7 +19,6 @@
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.Easing
 import androidx.compose.animation.core.LinearEasing
-import androidx.compose.animation.core.SpringSpec
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
@@ -37,12 +36,6 @@
 @TransitionDsl
 interface SceneTransitionsBuilder {
     /**
-     * The default [AnimationSpec] used when after the user lifts their finger after starting a
-     * swipe to transition, to animate back into one of the 2 scenes we are transitioning to.
-     */
-    var defaultMotionSpatialSpec: SpringSpec<Float>
-
-    /**
      * The [InterruptionHandler] used when transitions are interrupted. Defaults to
      * [DefaultInterruptionHandler].
      */
@@ -139,15 +132,7 @@
      * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
      * the transition is triggered (i.e. it is not gesture-based).
      */
-    var spec: AnimationSpec<Float>
-
-    /**
-     * The [SpringSpec] used to animate the associated transition progress when the transition was
-     * started by a swipe and is now animating back to a scene because the user lifted their finger.
-     *
-     * If `null`, then the [SceneTransitionsBuilder.defaultMotionSpatialSpec] will be used.
-     */
-    var motionSpatialSpec: SpringSpec<Float>?
+    var spec: AnimationSpec<Float>?
 
     /** The CUJ associated to this transitions. */
     @CujType var cuj: Int?
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 9a9b05e..ccb7024 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -19,10 +19,7 @@
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.DurationBasedAnimationSpec
 import androidx.compose.animation.core.Easing
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.VectorConverter
-import androidx.compose.animation.core.spring
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.Dp
 import com.android.compose.animation.scene.content.state.TransitionState
@@ -42,14 +39,12 @@
 internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
     val impl = SceneTransitionsBuilderImpl().apply(builder)
     return SceneTransitions(
-        defaultMotionSpatialSpec = impl.defaultMotionSpatialSpec,
         transitionSpecs = impl.transitionSpecs,
         interruptionHandler = impl.interruptionHandler,
     )
 }
 
 private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
-    override var defaultMotionSpatialSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec
     override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler
 
     val transitionSpecs = mutableListOf<TransitionSpecImpl>()
@@ -109,7 +104,6 @@
             val impl = TransitionBuilderImpl(transition).apply(builder)
             return TransformationSpecImpl(
                 progressSpec = impl.spec,
-                motionSpatialSpec = impl.motionSpatialSpec,
                 distance = impl.distance,
                 transformationMatchers = impl.transformationMatchers,
             )
@@ -212,8 +206,7 @@
 
 internal class TransitionBuilderImpl(override val transition: TransitionState.Transition) :
     BaseTransitionBuilderImpl(), TransitionBuilder {
-    override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
-    override var motionSpatialSpec: SpringSpec<Float>? = null
+    override var spec: AnimationSpec<Float>? = null
     override var distance: UserActionDistance? = null
     override var cuj: Int? = null
     private val durationMillis: Int by lazy {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 59b4a09..6ccd498 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -17,8 +17,12 @@
 package com.android.compose.animation.scene.content
 
 import android.annotation.SuppressLint
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.AnimationVector
+import androidx.compose.animation.core.TwoWayConverter
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
@@ -27,6 +31,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.LookaheadScope
 import androidx.compose.ui.layout.approachLayout
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.unit.IntSize
@@ -119,16 +124,28 @@
 
     override val layoutState: SceneTransitionLayoutState = layoutImpl.state
 
+    override val lookaheadScope: LookaheadScope
+        get() = layoutImpl.lookaheadScope
+
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    private val animationSpatialSpec =
+        object : AnimationSpec<Float> {
+            override fun <V : AnimationVector> vectorize(converter: TwoWayConverter<Float, V>) =
+                layoutImpl.state.motionScheme.defaultSpatialSpec<Float>().vectorize(converter)
+        }
+
     private val _verticalOverscrollEffect =
         OffsetOverscrollEffect(
             orientation = Orientation.Vertical,
             animationScope = layoutImpl.animationScope,
+            animationSpec = animationSpatialSpec,
         )
 
     private val _horizontalOverscrollEffect =
         OffsetOverscrollEffect(
             orientation = Orientation.Horizontal,
             animationScope = layoutImpl.animationScope,
+            animationSpec = animationSpatialSpec,
         )
 
     val verticalOverscrollGestureEffect = GestureEffect(_verticalOverscrollEffect)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index e9542c8..e23e234 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -18,8 +18,7 @@
 
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
@@ -385,14 +384,13 @@
             fun create(): Animatable<Float, AnimationVector1D> {
                 val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold)
                 layoutImpl.animationScope.launch {
-                    val motionSpatialSpec = layoutImpl.state.transitions.defaultMotionSpatialSpec
-                    val progressSpec =
-                        spring(
-                            stiffness = motionSpatialSpec.stiffness,
-                            dampingRatio = Spring.DampingRatioNoBouncy,
-                            visibilityThreshold = ProgressVisibilityThreshold,
-                        )
-                    animatable.animateTo(0f, progressSpec)
+                    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+                    animatable.animateTo(
+                        targetValue = 0f,
+                        // Quickly animate (use fast) the current transition and without bounces
+                        // (use effects). A new transition will start soon.
+                        animationSpec = layoutImpl.state.motionScheme.fastEffectsSpec(),
+                    )
                 }
 
                 return animatable
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
index 00cd0ca..2134510 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
@@ -20,7 +20,6 @@
 import androidx.compose.animation.core.DeferredTargetAnimation
 import androidx.compose.animation.core.ExperimentalAnimatableApi
 import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.animation.core.spring
 import androidx.compose.ui.unit.Dp
@@ -103,14 +102,6 @@
     // The spring animating the alpha of the container.
     val alphaSpec = spring<Float>(stiffness = 1200f, dampingRatio = 0.99f)
 
-    // The spring animating the progress when releasing the finger.
-    motionSpatialSpec =
-        spring(
-            stiffness = Spring.StiffnessMediumLow,
-            dampingRatio = Spring.DampingRatioNoBouncy,
-            visibilityThreshold = 0.5f,
-        )
-
     // Size transformation.
     transformation(container) {
         VerticalContainerRevealSizeTransformation(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 62ec221..d11e6da1 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -386,7 +386,7 @@
     @Test
     fun animatedValueIsUsingLastTransition() = runTest {
         val state =
-            rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA, transitions {}) }
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA, transitions {}) }
 
         val foo = ValueKey("foo")
         val bar = ValueKey("bar")
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
index 06a9735..dc69426 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
@@ -42,7 +42,7 @@
         lateinit var layoutImpl: SceneTransitionLayoutImpl
         rule.setContent {
             SceneTransitionLayoutForTesting(
-                remember { MutableSceneTransitionLayoutState(SceneA) },
+                remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
                 onLayoutImpl = { layoutImpl = it },
             ) {
                 scene(SceneA) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index ef36077..35ff004 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -60,7 +60,7 @@
     private class TestGestureScope(val testScope: MonotonicClockTestScope) {
         var canChangeScene: (SceneKey) -> Boolean = { true }
         val layoutState =
-            MutableSceneTransitionLayoutStateImpl(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 EmptyTestTransitions,
                 canChangeScene = { canChangeScene(it) },
@@ -640,10 +640,7 @@
     @Test
     fun overscroll_releaseBetween0And100Percent_up() = runGestureTest {
         // Make scene B overscrollable.
-        layoutState.transitions = transitions {
-            defaultMotionSpatialSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
-            from(SceneA, to = SceneB) {}
-        }
+        layoutState.transitions = transitions { from(SceneA, to = SceneB) {} }
 
         val dragController =
             onDragStarted(
@@ -671,10 +668,7 @@
     @Test
     fun overscroll_releaseBetween0And100Percent_down() = runGestureTest {
         // Make scene C overscrollable.
-        layoutState.transitions = transitions {
-            defaultMotionSpatialSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
-            from(SceneA, to = SceneC) {}
-        }
+        layoutState.transitions = transitions { from(SceneA, to = SceneC) {} }
 
         val dragController =
             onDragStarted(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index b405fbe..8d0af9b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -209,7 +209,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneA, to = SceneB) { spec = tween }
@@ -343,7 +343,7 @@
 
     @Test
     fun elementIsReusedBetweenScenes() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         var sceneCState by mutableStateOf(0)
         val key = TestElements.Foo
         var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
@@ -473,7 +473,7 @@
 
     @Test
     fun elementModifierSupportsUpdates() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         var key by mutableStateOf(TestElements.Foo)
         var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
 
@@ -523,7 +523,7 @@
             rule.waitUntil(timeoutMillis = 10_000) { animationFinished }
         }
 
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         rule.setContent {
             scrollScope = rememberCoroutineScope()
 
@@ -618,7 +618,7 @@
     fun layoutGetsCurrentTransitionStateFromComposition() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneA, to = SceneB) {
@@ -663,7 +663,8 @@
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+        val state =
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
         rule.setContent {
             density = LocalDensity.current
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -718,7 +719,8 @@
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+        val state =
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
         rule.setContent {
             density = LocalDensity.current
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -813,7 +815,8 @@
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+        val state =
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
         rule.setContent {
             density = LocalDensity.current
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -866,7 +869,8 @@
         val layoutWidth = 200.dp
         val layoutHeight = 400.dp
 
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+        val state =
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
 
         rule.setContent {
             density = LocalDensity.current
@@ -934,7 +938,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     initialScene = SceneA,
                     transitions =
                         transitions {
@@ -943,7 +947,6 @@
                             }
                         },
                 )
-                    as MutableSceneTransitionLayoutStateImpl
             }
 
         rule.setContent {
@@ -999,7 +1002,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         // Foo is at the top left corner of scene A. We make it disappear during A
@@ -1126,7 +1129,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) }
@@ -1331,7 +1334,7 @@
         val fooSize = 100.dp
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) }
@@ -1439,7 +1442,7 @@
     @Test
     fun targetStateIsSetEvenWhenNotPlaced() {
         // Start directly at A => B but with progress < 0f to overscroll on A.
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         lateinit var layoutImpl: SceneTransitionLayoutImpl
         val scope =
@@ -1473,7 +1476,7 @@
     fun lastAlphaIsNotSetByOutdatedLayer() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } },
                 )
@@ -1537,7 +1540,7 @@
     fun fadingElementsDontAppearInstantly() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } },
                 )
@@ -1583,7 +1586,7 @@
 
     @Test
     fun lastPlacementValuesAreClearedOnNestedElements() {
-        val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         @Composable
         fun ContentScope.NestedFooBar() {
@@ -1658,7 +1661,7 @@
     fun currentTransitionSceneIsUsedToComputeElementValues() {
         val state =
             rule.runOnIdle {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneB, to = SceneC) {
@@ -1709,7 +1712,7 @@
 
     @Test
     fun interruptionDeltasAreProperlyCleaned() {
-        val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         @Composable
         fun ContentScope.Foo(offset: Dp) {
@@ -1780,7 +1783,7 @@
     fun transparentElementIsNotImpactingInterruption() {
         val state =
             rule.runOnIdle {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneA, to = SceneB) {
@@ -1856,7 +1859,7 @@
 
     @Test
     fun replacedTransitionDoesNotTriggerInterruption() {
-        val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         @Composable
         fun ContentScope.Foo(modifier: Modifier = Modifier) {
@@ -2027,7 +2030,7 @@
     ): SceneTransitionLayoutImpl {
         val state =
             rule.runOnIdle {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     from,
                     transitions { from(from, to = to, preview = preview, builder = transition) },
                 )
@@ -2174,7 +2177,7 @@
             )
         }
 
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         val scope =
             rule.setContentAndCreateMainScope {
                 SceneTransitionLayout(state) {
@@ -2215,7 +2218,7 @@
             Box(modifier.element(TestElements.Foo).size(50.dp))
         }
 
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         val scope =
             rule.setContentAndCreateMainScope {
                 SceneTransitionLayout(state) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
index 3622369..b44f552 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
@@ -39,7 +39,7 @@
     @Test
     fun default() = runMonotonicClockTest {
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions { /* default interruption handler */ },
             )
@@ -62,7 +62,7 @@
     @Test
     fun chainingDisabled() = runMonotonicClockTest {
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions {
                     // Handler that animates from currentScene (default) but disables chaining.
@@ -97,7 +97,7 @@
     fun animateFromOtherScene() = runMonotonicClockTest {
         val duration = 500
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions {
                     // Handler that animates from the scene that is not currentScene.
@@ -146,7 +146,7 @@
 
     @Test
     fun animateToFromScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, transitions {})
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions {})
 
         // Fake a transition from A to B that has a non 0 velocity.
         val progressVelocity = 1f
@@ -182,7 +182,7 @@
 
     @Test
     fun animateToToScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, transitions {})
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions {})
 
         // Fake a transition from A to B with current scene = A that has a non 0 velocity.
         val progressVelocity = -1f
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 9e1bae5..8d718c1 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -380,7 +380,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     initialScene = SceneA,
                     initialOverlays = setOf(OverlayA),
                 )
@@ -420,7 +420,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     initialScene = SceneA,
                     initialOverlays = setOf(OverlayA),
                 )
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt
new file mode 100644
index 0000000..4326ec9
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MotionScheme
+import com.android.compose.animation.scene.content.state.TransitionState
+
+internal fun MutableSceneTransitionLayoutStateForTests(
+    initialScene: SceneKey,
+    transitions: SceneTransitions = SceneTransitions.Empty,
+    initialOverlays: Set<OverlayKey> = emptySet(),
+    canChangeScene: (SceneKey) -> Boolean = { true },
+    canShowOverlay: (OverlayKey) -> Boolean = { true },
+    canHideOverlay: (OverlayKey) -> Boolean = { true },
+    canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
+    onTransitionStart: (TransitionState.Transition) -> Unit = {},
+    onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+): MutableSceneTransitionLayoutStateImpl {
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    return MutableSceneTransitionLayoutStateImpl(
+        initialScene,
+        motionScheme = MotionScheme.standard(),
+        transitions,
+        initialOverlays,
+        canChangeScene,
+        canShowOverlay,
+        canHideOverlay,
+        canReplaceOverlay,
+        onTransitionStart,
+        onTransitionEnd,
+    )
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
index 596e2cd..f2c0ca5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
@@ -47,7 +47,9 @@
     @Test
     fun testObservableTransitionState() = runTest {
         val state =
-            rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA, EmptyTestTransitions) }
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
+            }
 
         // Collect the current observable state into [observableState].
         // TODO(b/290184746): Use collectValues {} once it is extracted into a library that can be
@@ -106,7 +108,7 @@
     @Test
     fun observableCurrentScene() = runTestWithSnapshots {
         val state =
-            MutableSceneTransitionLayoutStateImpl(
+            MutableSceneTransitionLayoutStateForTests(
                 initialScene = SceneA,
                 transitions = transitions {},
             )
@@ -150,7 +152,7 @@
     fun testObservablePreviewTransitionState() = runTest {
         val layoutState =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions = transitions { from(SceneA, to = SceneB, preview = {}) },
                 )
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index 93fa516..50bfbfe 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -73,7 +73,7 @@
 
     @Test
     fun showThenHideOverlay() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         lateinit var coroutineScope: CoroutineScope
         rule.setContent {
             coroutineScope = rememberCoroutineScope()
@@ -115,7 +115,7 @@
 
     @Test
     fun multipleOverlays() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         lateinit var coroutineScope: CoroutineScope
         rule.setContent {
             coroutineScope = rememberCoroutineScope()
@@ -213,7 +213,7 @@
             }
         }
 
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         lateinit var coroutineScope: CoroutineScope
         rule.setContent {
             coroutineScope = rememberCoroutineScope()
@@ -285,7 +285,7 @@
     fun overlayAlignment() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+                MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
             }
         var alignment by mutableStateOf(Alignment.Center)
         rule.setContent {
@@ -320,7 +320,7 @@
     fun overlayMaxSizeIsCurrentSceneSize() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+                MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
             }
 
         val contentTag = "overlayContent"
@@ -742,7 +742,7 @@
 
     @Test
     fun overscrollingOverlay_movableElementNotInOverlay() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         val key = MovableElementKey("Foo", contents = setOf(SceneA))
         val movableElementChildTag = "movableElementChildTag"
@@ -769,7 +769,7 @@
 
     @Test
     fun overlaysAreModalByDefault() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         val scrollState = ScrollState(initial = 0)
         val scope =
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index 4224a0c..9f15ebd 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -50,7 +50,7 @@
 
     @Test
     fun testBack() {
-        val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         rule.setContent {
             SceneTransitionLayout(layoutState) {
                 scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
@@ -70,7 +70,7 @@
         val transitionFrames = 2
         val layoutState =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions =
                         transitions {
@@ -142,7 +142,7 @@
     fun testPredictiveBackWithPreview() {
         val layoutState =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions = transitions { from(SceneA, to = SceneB, preview = {}) },
                 )
@@ -192,7 +192,7 @@
         var canChangeSceneCalled = false
         val layoutState =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     canChangeScene = {
                         canChangeSceneCalled = true
@@ -241,7 +241,7 @@
     fun backDismissesOverlayWithHighestZIndexByDefault() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     initialOverlays = setOf(OverlayA, OverlayB),
                 )
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index f3be5e4..c5e4061 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -51,7 +51,7 @@
 
     @Test
     fun isTransitioningTo_idle() {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, SceneTransitions.Empty)
 
         assertThat(state.isTransitioning()).isFalse()
         assertThat(state.isTransitioning(from = SceneA)).isFalse()
@@ -61,7 +61,7 @@
 
     @Test
     fun isTransitioningTo_transition() = runTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, SceneTransitions.Empty)
         state.startTransitionImmediately(
             animationScope = backgroundScope,
             transition(from = SceneA, to = SceneB),
@@ -77,13 +77,13 @@
 
     @Test
     fun setTargetScene_idleToSameScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         assertThat(state.setTargetScene(SceneA, animationScope = this)).isNull()
     }
 
     @Test
     fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         val (transition, job) = checkNotNull(state.setTargetScene(SceneB, animationScope = this))
         assertThat(state.transitionState).isEqualTo(transition)
 
@@ -93,7 +93,7 @@
 
     @Test
     fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         val (_, job) = checkNotNull(state.setTargetScene(SceneB, animationScope = this))
         assertThat(state.setTargetScene(SceneB, animationScope = this)).isNull()
@@ -104,7 +104,7 @@
 
     @Test
     fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         assertThat(state.setTargetScene(SceneB, animationScope = this)).isNotNull()
         val (_, job) = checkNotNull(state.setTargetScene(SceneC, animationScope = this))
@@ -115,7 +115,7 @@
 
     @Test
     fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         lateinit var transition: TransitionState.Transition
         val job =
@@ -133,7 +133,7 @@
     fun setTargetScene_withTransitionKey() = runMonotonicClockTest {
         val transitionkey = TransitionKey(debugName = "foo")
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions =
                     transitions {
@@ -176,7 +176,7 @@
             return { /* do nothing */ }
         }
 
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
         val aToB = transition(SceneA, SceneB, onFreezeAndAnimate = ::onFreezeAndAnimate)
         val bToC = transition(SceneB, SceneC, onFreezeAndAnimate = ::onFreezeAndAnimate)
         val cToA = transition(SceneC, SceneA, onFreezeAndAnimate = ::onFreezeAndAnimate)
@@ -226,7 +226,7 @@
 
     @Test
     fun tooManyTransitionsLogsWtfAndClearsTransitions() = runTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
 
         fun startTransition() {
             val transition =
@@ -251,7 +251,7 @@
 
     @Test
     fun snapToScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         // Transition to B.
         state.setTargetScene(SceneB, animationScope = this)
@@ -266,7 +266,7 @@
 
     @Test
     fun snapToScene_freezesCurrentTransition() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         // Start a transition that is never finished. We don't use backgroundScope on purpose so
         // that this test would fail if the transition was not frozen when snapping.
@@ -283,7 +283,7 @@
 
     @Test
     fun seekToScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         val progress = Channel<Float>()
 
         val job =
@@ -309,7 +309,7 @@
 
     @Test
     fun seekToScene_cancelled() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         val progress = Channel<Float>()
 
         val job =
@@ -335,7 +335,7 @@
 
     @Test
     fun seekToScene_interrupted() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         val progress = Channel<Float>()
 
         val job =
@@ -354,7 +354,7 @@
 
     @Test
     fun replacedTransitionIsRemovedFromFinishedTransitions() = runTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         val aToB =
             transition(
@@ -403,7 +403,7 @@
 
     @Test
     fun transitionCanBeStartedOnlyOnce() = runTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         val transition = transition(from = SceneA, to = SceneB)
 
         state.startTransitionImmediately(backgroundScope, transition)
@@ -414,7 +414,7 @@
 
     @Test
     fun transitionFinishedWhenScopeIsEmpty() = runTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         // Start a transition.
         val transition = transition(from = SceneA, to = SceneB)
@@ -439,7 +439,7 @@
 
     @Test
     fun transitionScopeIsCancelledWhenTransitionIsForceFinished() = runTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
 
         // Start a transition.
         val transition = transition(from = SceneA, to = SceneB)
@@ -458,7 +458,7 @@
     @Test
     fun badTransitionSpecThrowsMeaningfulMessageWhenStartingTransition() {
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions {
                     // This transition definition is bad because they both match when transitioning
@@ -483,7 +483,7 @@
 
     @Test
     fun snapToScene_multipleTransitions() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA)
         state.startTransitionImmediately(this, transition(SceneA, SceneB))
         state.startTransitionImmediately(this, transition(SceneB, SceneC))
         state.snapToScene(SceneC)
@@ -498,7 +498,7 @@
         val finished = mutableSetOf<TransitionState.Transition>()
         val cujWhenStarting = mutableMapOf<TransitionState.Transition, Int?>()
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions {
                     // A <=> B.
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index e580e3c..3c490ae 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -17,7 +17,9 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.FiniteAnimationSpec
 import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
@@ -25,9 +27,13 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MotionScheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
@@ -60,7 +66,6 @@
 import com.android.compose.test.assertSizeIsEqualTo
 import com.android.compose.test.subjects.DpOffsetSubject
 import com.android.compose.test.subjects.assertThat
-import com.android.compose.test.transition
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import org.junit.Assert.assertThrows
@@ -88,7 +93,9 @@
     @Composable
     private fun TestContent() {
         coroutineScope = rememberCoroutineScope()
-        layoutState = remember { MutableSceneTransitionLayoutState(SceneA, EmptyTestTransitions) }
+        layoutState = remember {
+            MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
+        }
 
         SceneTransitionLayout(state = layoutState, modifier = Modifier.size(LayoutSize)) {
             scene(SceneA, userActions = mapOf(Back to SceneB)) {
@@ -317,7 +324,7 @@
 
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     SceneA,
                     transitions {
                         from(SceneA, to = SceneB) {
@@ -421,7 +428,7 @@
             assertThrows(IllegalStateException::class.java) {
                 rule.setContent {
                     SceneTransitionLayout(
-                        state = remember { MutableSceneTransitionLayoutState(SceneA) },
+                        state = remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
                         modifier = Modifier.size(LayoutSize),
                     ) {
                         // from SceneA to SceneA
@@ -436,7 +443,7 @@
 
     @Test
     fun sceneKeyInScope() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         var keyInA: ContentKey? = null
         var keyInB: ContentKey? = null
@@ -465,7 +472,7 @@
         lateinit var layoutImpl: SceneTransitionLayoutImpl
         rule.setContent {
             SceneTransitionLayoutForTesting(
-                remember { MutableSceneTransitionLayoutState(SceneA) },
+                remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
                 onLayoutImpl = { layoutImpl = it },
             ) {
                 scene(SceneA) { Box(Modifier.fillMaxSize()) }
@@ -483,7 +490,8 @@
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
         var touchSlop = 0f
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+        val state =
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
             SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
@@ -511,4 +519,67 @@
         // Fling animation, we are overscrolling now. Progress should always be between [0, 1].
         assertThat(transition).hasProgress(1f)
     }
+
+    @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+    @Test
+    fun motionSchemeArePassedToSTLState() {
+        // Implementation inspired by MotionScheme.standard()
+        @Suppress("UNCHECKED_CAST")
+        fun motionScheme(animationSpec: FiniteAnimationSpec<Any>) =
+            object : MotionScheme {
+                override fun <T> defaultEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+                override fun <T> defaultSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+
+                override fun <T> fastEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+                override fun <T> fastSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+
+                override fun <T> slowEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+                override fun <T> slowSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+            }
+
+        lateinit var state1: MutableSceneTransitionLayoutState
+        lateinit var state2: MutableSceneTransitionLayoutState
+
+        lateinit var motionScheme1: MotionScheme
+        var motionScheme2 by mutableStateOf(motionScheme(animationSpec = tween(500)))
+        rule.setContent {
+            motionScheme1 = MaterialTheme.motionScheme
+            state1 = rememberMutableSceneTransitionLayoutState(initialScene = SceneA)
+            SceneTransitionLayout(state1) {
+                scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+                    Spacer(Modifier.fillMaxSize())
+                }
+            }
+
+            MaterialTheme(motionScheme = motionScheme2) {
+                // Important: we should read this state inside the MaterialTheme composable.
+                state2 = rememberMutableSceneTransitionLayoutState(initialScene = SceneA)
+                SceneTransitionLayout(state2) {
+                    scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+                        Spacer(Modifier.fillMaxSize())
+                    }
+                }
+            }
+        }
+
+        assertThat(motionScheme1).isNotNull()
+        assertThat(motionScheme1).isNotEqualTo(motionScheme2)
+
+        assertThat((state1 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+            .isEqualTo(motionScheme1)
+
+        assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+            .isEqualTo(motionScheme2)
+
+        // Update the MaterialTheme's MotionScheme configuration.
+        motionScheme2 = motionScheme(animationSpec = spring())
+
+        // We just updated the motionScheme2 state, wait for a recomposition.
+        rule.waitForIdle()
+        assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+            .isEqualTo(motionScheme2)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index e036084..751b314 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -93,7 +93,9 @@
         initialScene: SceneKey = SceneA,
         transitions: SceneTransitions = EmptyTestTransitions,
     ): MutableSceneTransitionLayoutState {
-        return rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene, transitions) }
+        return rule.runOnUiThread {
+            MutableSceneTransitionLayoutStateForTests(initialScene, transitions)
+        }
     }
 
     /** The content under test. */
@@ -738,7 +740,7 @@
     fun startEnd_ltrLayout() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     initialScene = SceneA,
                     transitions =
                         transitions {
@@ -811,7 +813,7 @@
     fun startEnd_rtlLayout() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(
+                MutableSceneTransitionLayoutStateForTests(
                     initialScene = SceneA,
                     transitions =
                         transitions {
@@ -893,7 +895,9 @@
                     Text("Count: $count")
                 }
 
-                SceneTransitionLayout(remember { MutableSceneTransitionLayoutState(SceneA) }) {
+                SceneTransitionLayout(
+                    remember { MutableSceneTransitionLayoutStateForTests(SceneA) }
+                ) {
                     scene(SceneA) { Box(Modifier.fillMaxSize()) }
                 }
             }
@@ -909,7 +913,7 @@
 
     @Test
     fun swipeToSceneSupportsUpdates() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
 
         rule.setContent {
             SceneTransitionLayout(state) {
@@ -943,7 +947,7 @@
     @Test
     fun swipeToSceneNodeIsKeptWhenDisabled() {
         var hasHorizontalActions by mutableStateOf(false)
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         var touchSlop = 0f
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -983,7 +987,7 @@
 
     @Test
     fun nestedScroll_useFromSourceInfo() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         var touchSlop = 0f
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1033,7 +1037,7 @@
 
     @Test
     fun nestedScroll_ignoreMouseWheel() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         var touchSlop = 0f
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1057,7 +1061,7 @@
 
     @Test
     fun nestedScroll_keepPriorityEvenIfWeCanNoLongerScrollOnThatDirection() {
-        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
         var touchSlop = 0f
         rule.setContent {
             touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1097,7 +1101,7 @@
     fun nestedScroll_replaceOverlay() {
         val state =
             rule.runOnUiThread {
-                MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+                MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
             }
         var touchSlop = 0f
         rule.setContent {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index aada4a50..e098aac 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -17,9 +17,7 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.animation.core.CubicBezierEasing
-import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.TweenSpec
-import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import androidx.compose.ui.unit.IntSize
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -84,7 +82,7 @@
     fun defaultTransitionSpec() {
         val transitions = transitions { from(SceneA, to = SceneB) }
         val transformationSpec = transitions.transitionSpecs.single().transformationSpec(aToB())
-        assertThat(transformationSpec.progressSpec).isInstanceOf(SpringSpec::class.java)
+        assertThat(transformationSpec.progressSpec).isNull()
     }
 
     @Test
@@ -272,44 +270,10 @@
     }
 
     @Test
-    fun springSpec() {
-        val defaultSpec = spring<Float>(stiffness = 1f)
-        val specFromAToC = spring<Float>(stiffness = 2f)
-        val transitions = transitions {
-            defaultMotionSpatialSpec = defaultSpec
-
-            from(SceneA, to = SceneB) {
-                // Default swipe spec.
-            }
-            from(SceneA, to = SceneC) { motionSpatialSpec = specFromAToC }
-        }
-
-        assertThat(transitions.defaultMotionSpatialSpec).isSameInstanceAs(defaultSpec)
-
-        // A => B does not have a custom spec.
-        assertThat(
-                transitions
-                    .transitionSpec(from = SceneA, to = SceneB, key = null)
-                    .transformationSpec(aToB())
-                    .motionSpatialSpec
-            )
-            .isNull()
-
-        // A => C has a custom swipe spec.
-        assertThat(
-                transitions
-                    .transitionSpec(from = SceneA, to = SceneC, key = null)
-                    .transformationSpec(transition(from = SceneA, to = SceneC))
-                    .motionSpatialSpec
-            )
-            .isSameInstanceAs(specFromAToC)
-    }
-
-    @Test
     fun transitionIsPassedToBuilder() = runTest {
         var transitionPassedToBuilder: TransitionState.Transition? = null
         val state =
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 SceneA,
                 transitions { from(SceneA, to = SceneB) { transitionPassedToBuilder = transition } },
             )
@@ -340,7 +304,7 @@
             }
         }
 
-        val state = MutableSceneTransitionLayoutState(SceneA, transitions)
+        val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions)
         assertThrows(IllegalStateException::class.java) {
             runBlocking { state.startTransition(transition(from = SceneA, to = SceneB)) }
         }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
index 0da422b..bb511bc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
@@ -37,6 +37,7 @@
 import com.android.compose.animation.scene.ContentScope
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
 import com.android.compose.animation.scene.Scale
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
@@ -104,7 +105,9 @@
         startScene: SceneKey,
         transitions: SceneTransitions = SceneTransitions.Empty,
     ): MutableSceneTransitionLayoutState {
-        return rule.runOnUiThread { MutableSceneTransitionLayoutState(startScene, transitions) }
+        return rule.runOnUiThread {
+            MutableSceneTransitionLayoutStateForTests(startScene, transitions)
+        }
     }
 
     private val threeNestedStls:
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
index d8b7136..83dd6d3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
@@ -39,6 +39,7 @@
 import com.android.compose.animation.scene.Default4FrameLinearTransition
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TestElements
 import com.android.compose.animation.scene.TestScenes
@@ -94,7 +95,7 @@
 
     private val nestedState: MutableSceneTransitionLayoutState =
         rule.runOnUiThread {
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 Scenes.NestedSceneA,
                 transitions {
                     from(
@@ -108,7 +109,7 @@
 
     private val nestedNestedState: MutableSceneTransitionLayoutState =
         rule.runOnUiThread {
-            MutableSceneTransitionLayoutState(
+            MutableSceneTransitionLayoutStateForTests(
                 Scenes.NestedNestedSceneA,
                 transitions {
                     from(
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
index 5cccfb1..6d47bab 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
@@ -29,6 +29,6 @@
     currentScene: SceneKey = remember { SceneKey("current") },
     content: @Composable ContentScope.() -> Unit,
 ) {
-    val state = remember { MutableSceneTransitionLayoutState(currentScene) }
+    val state = rememberMutableSceneTransitionLayoutState(currentScene)
     SceneTransitionLayout(state, modifier) { scene(currentScene, content = content) }
 }
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index bc160fc..f94a7ed 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -129,13 +129,12 @@
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        state =
-            runOnUiThread {
-                MutableSceneTransitionLayoutState(
-                    fromScene,
-                    transitions { from(fromScene, to = toScene, builder = transition) },
-                )
-            },
+        state = {
+            rememberMutableSceneTransitionLayoutState(
+                fromScene,
+                transitions { from(fromScene, to = toScene, builder = transition) },
+            )
+        },
         changeState = changeState,
         transitionLayout = { state ->
             SceneTransitionLayout(state, layoutModifier) {
@@ -157,13 +156,12 @@
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        state =
-            runOnUiThread {
-                MutableSceneTransitionLayoutState(
-                    fromScene,
-                    transitions = transitions { from(fromScene, overlay, builder = transition) },
-                )
-            },
+        state = {
+            rememberMutableSceneTransitionLayoutState(
+                fromScene,
+                transitions = transitions { from(fromScene, overlay, builder = transition) },
+            )
+        },
         transitionLayout = { state ->
             SceneTransitionLayout(state) {
                 scene(fromScene) { fromSceneContent() }
@@ -185,14 +183,13 @@
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        state =
-            runOnUiThread {
-                MutableSceneTransitionLayoutState(
-                    toScene,
-                    initialOverlays = setOf(overlay),
-                    transitions = transitions { from(overlay, toScene, builder = transition) },
-                )
-            },
+        state = {
+            rememberMutableSceneTransitionLayoutState(
+                toScene,
+                initialOverlays = setOf(overlay),
+                transitions = transitions { from(overlay, toScene, builder = transition) },
+            )
+        },
         transitionLayout = { state ->
             SceneTransitionLayout(state) {
                 scene(toScene) { toSceneContent() }
@@ -218,14 +215,13 @@
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        state =
-            runOnUiThread {
-                MutableSceneTransitionLayoutState(
-                    currentScene,
-                    initialOverlays = setOf(from),
-                    transitions = transitions { from(from, to, builder = transition) },
-                )
-            },
+        state = {
+            rememberMutableSceneTransitionLayoutState(
+                currentScene,
+                initialOverlays = setOf(from),
+                transitions = transitions { from(from, to, builder = transition) },
+            )
+        },
         transitionLayout = { state ->
             SceneTransitionLayout(state) {
                 scene(currentScene) { currentSceneContent() }
@@ -263,16 +259,14 @@
     fromScene: SceneKey = TestScenes.SceneA,
     toScene: SceneKey = TestScenes.SceneB,
 ): RecordedMotion {
-    val state =
-        toolkit.composeContentTestRule.runOnUiThread {
-            MutableSceneTransitionLayoutState(
-                fromScene,
-                transitions { from(fromScene, to = toScene, builder = transition) },
-            )
-        }
-
+    lateinit var state: MutableSceneTransitionLayoutState
     return recordMotion(
         content = { play ->
+            state =
+                rememberMutableSceneTransitionLayoutState(
+                    fromScene,
+                    transitions { from(fromScene, to = toScene, builder = transition) },
+                )
             LaunchedEffect(play) {
                 if (play) {
                     state.setTargetScene(toScene, animationScope = this)
@@ -309,7 +303,7 @@
     }
 
     testTransition(
-        state = state,
+        state = { state },
         changeState = { state -> state.setTargetScene(to, animationScope = this) },
         transitionLayout = transitionLayout,
         builder = builder,
@@ -323,7 +317,7 @@
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
-        state = states[0],
+        state = { states[0] },
         changeState = { changeState(states) },
         transitionLayout = { transitionLayout(states) },
         builder = builder,
@@ -331,16 +325,18 @@
 }
 
 /** Test the transition from [state] to [to]. */
-fun ComposeContentTestRule.testTransition(
-    state: MutableSceneTransitionLayoutState,
+private fun ComposeContentTestRule.testTransition(
+    state: @Composable () -> MutableSceneTransitionLayoutState,
     changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit,
     transitionLayout: @Composable (state: MutableSceneTransitionLayoutState) -> Unit,
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     lateinit var coroutineScope: CoroutineScope
+    lateinit var layoutState: MutableSceneTransitionLayoutState
     setContent {
+        layoutState = state()
         coroutineScope = rememberCoroutineScope()
-        transitionLayout(state)
+        transitionLayout(layoutState)
     }
 
     val assertionScope =
@@ -390,7 +386,7 @@
     mainClock.autoAdvance = false
 
     // Change the current scene.
-    runOnUiThread { coroutineScope.changeState(state) }
+    runOnUiThread { coroutineScope.changeState(layoutState) }
     waitForIdle()
     mainClock.advanceTimeByFrame()
     waitForIdle()
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
index 4b5e9de..72304a1 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
@@ -75,6 +75,7 @@
     private val maxSize: Int,
     private val logcatEchoTracker: LogcatEchoTracker,
     private val systrace: Boolean = true,
+    private val systraceTrackName: String = DEFAULT_LOGBUFFER_TRACK_NAME,
 ) : MessageBuffer {
     private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
 
@@ -244,10 +245,11 @@
     }
 
     private fun echoToSystrace(level: LogLevel, tag: String, strMessage: String) {
+        if (!Trace.isEnabled()) return
         Trace.instantForTrack(
             Trace.TRACE_TAG_APP,
-            "UI Events",
-            "$name - ${level.shortString} $tag: $strMessage"
+            systraceTrackName,
+            "$name - ${level.shortString} $tag: $strMessage",
         )
     }
 
@@ -261,6 +263,10 @@
             LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
         }
     }
+
+    companion object {
+        const val DEFAULT_LOGBUFFER_TRACK_NAME = "UI Events"
+    }
 }
 
 private const val TAG = "LogBuffer"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index 73efea7..2713bb0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -316,5 +316,7 @@
         dialog.onConfigurationChanged(config)
         testableLooper.processAllMessages()
         assertThat(doneButton.isEnabled).isTrue()
+
+        dialog.dismiss()
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
index e531e65..00d5afe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -16,14 +16,17 @@
 
 package com.android.systemui.communal
 
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.service.dream.dreamManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -48,12 +51,14 @@
 import org.junit.runner.RunWith
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
-@RunWith(AndroidJUnit4::class)
-class CommunalDreamStartableTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalDreamStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
@@ -63,26 +68,50 @@
     private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
     private val powerRepository by lazy { kosmos.fakePowerRepository }
 
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
     @Before
     fun setUp() {
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
 
         underTest =
             CommunalDreamStartable(
-                    powerInteractor = kosmos.powerInteractor,
-                    communalSettingsInteractor = kosmos.communalSettingsInteractor,
-                    keyguardInteractor = kosmos.keyguardInteractor,
-                    keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
-                    dreamManager = dreamManager,
-                    communalSceneInteractor = kosmos.communalSceneInteractor,
-                    bgScope = kosmos.applicationCoroutineScope,
-                )
-                .apply { start() }
+                powerInteractor = kosmos.powerInteractor,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
+                keyguardInteractor = kosmos.keyguardInteractor,
+                keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+                dreamManager = dreamManager,
+                communalSceneInteractor = kosmos.communalSceneInteractor,
+                bgScope = kosmos.applicationCoroutineScope,
+            )
     }
 
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    @Test
+    fun dreamNotStartedWhenTransitioningToHub() =
+        testScope.runTest {
+            // Enable v2 flag and recreate + rerun start method.
+            kosmos.setCommunalV2Enabled(true)
+            underTest.start()
+
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setDreaming(false)
+            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+            whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
+            runCurrent()
+
+            transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+            verify(dreamManager, never()).startDream()
+        }
+
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun startDreamWhenTransitioningToHub() =
         testScope.runTest {
+            underTest.start()
             keyguardRepository.setKeyguardShowing(true)
             keyguardRepository.setDreaming(false)
             powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
@@ -100,6 +129,7 @@
     @EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE)
     fun restartDreamingWhenTransitioningFromDreamingToOccludedToDreaming() =
         testScope.runTest {
+            underTest.start()
             keyguardRepository.setDreaming(false)
             powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
             whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
@@ -122,9 +152,11 @@
             verify(dreamManager).startDream()
         }
 
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun shouldNotStartDreamWhenIneligibleToDream() =
         testScope.runTest {
+            underTest.start()
             keyguardRepository.setDreaming(false)
             powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
             // Not eligible to dream
@@ -134,9 +166,11 @@
             verify(dreamManager, never()).startDream()
         }
 
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun shouldNotStartDreamIfAlreadyDreaming() =
         testScope.runTest {
+            underTest.start()
             keyguardRepository.setDreaming(true)
             powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
             whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
@@ -145,9 +179,11 @@
             verify(dreamManager, never()).startDream()
         }
 
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun shouldNotStartDreamForInvalidTransition() =
         testScope.runTest {
+            underTest.start()
             keyguardRepository.setDreaming(true)
             powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
             whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
@@ -160,9 +196,11 @@
             }
         }
 
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun shouldNotStartDreamWhenLaunchingWidget() =
         testScope.runTest {
+            underTest.start()
             keyguardRepository.setKeyguardShowing(true)
             keyguardRepository.setDreaming(false)
             powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
@@ -175,9 +213,11 @@
             verify(dreamManager, never()).startDream()
         }
 
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun shouldNotStartDreamWhenOccluded() =
         testScope.runTest {
+            underTest.start()
             keyguardRepository.setKeyguardShowing(true)
             keyguardRepository.setDreaming(false)
             powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
@@ -194,8 +234,16 @@
         kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
             from = from,
             to = to,
-            testScope = this
+            testScope = this,
         )
         runCurrent()
     }
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 5921e94..0df584f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -58,6 +58,7 @@
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
 import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.complication.ComplicationHostViewController
@@ -747,7 +748,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
-    @DisableFlags(FLAG_SCENE_CONTAINER)
+    @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
     @kotlin.Throws(RemoteException::class)
     fun testTransitionToGlanceableHub() =
         testScope.runTest {
@@ -774,6 +775,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_SCENE_CONTAINER, FLAG_COMMUNAL_HUB)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @kotlin.Throws(RemoteException::class)
     fun testTransitionToGlanceableHub_sceneContainer() =
         testScope.runTest {
@@ -802,7 +804,29 @@
         }
 
     @Test
+    @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
+    @Throws(RemoteException::class)
+    fun testRedirect_v2Enabled_notTriggered() =
+        testScope.runTest {
+            kosmos.setCommunalV2Enabled(true)
+            // Inform the overlay service of dream starting. Do not show dream complications.
+            client.startDream(
+                mWindowParams,
+                mDreamOverlayCallback,
+                DREAM_COMPONENT,
+                false /*isPreview*/,
+                false, /*shouldShowComplication*/
+            )
+            // Set communal available, verify that onRedirectWake is never called.
+            kosmos.setCommunalAvailable(true)
+            mMainExecutor.runAllReady()
+            runCurrent()
+            verify(mDreamOverlayCallback, never()).onRedirectWake(any())
+        }
+
+    @Test
     @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Throws(RemoteException::class)
     fun testRedirectExit() =
         testScope.runTest {
@@ -1347,7 +1371,11 @@
         @JvmStatic
         @Parameters(name = "{0}")
         fun getParams(): List<FlagsParameterization> {
-            return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_HUB).andSceneContainer()
+            return FlagsParameterization.allCombinationsOf(
+                    FLAG_COMMUNAL_HUB,
+                    FLAG_GLANCEABLE_HUB_V2,
+                )
+                .andSceneContainer()
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index bf49186..451ebf3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -27,7 +27,7 @@
 import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.settingslib.notification.modes.EnableZenModeDialog
+import com.android.settingslib.notification.modes.EnableDndDialogFactory
 import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
@@ -85,7 +85,7 @@
     @Mock private lateinit var zenModeController: ZenModeController
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var conditionUri: Uri
-    @Mock private lateinit var enableZenModeDialog: EnableZenModeDialog
+    @Mock private lateinit var mEnableDndDialogFactory: EnableDndDialogFactory
     @Captor private lateinit var spyZenMode: ArgumentCaptor<Int>
     @Captor private lateinit var spyConditionId: ArgumentCaptor<Uri?>
 
@@ -105,7 +105,7 @@
                 testDispatcher,
                 testScope.backgroundScope,
                 conditionUri,
-                enableZenModeDialog,
+                mEnableDndDialogFactory,
             )
     }
 
@@ -322,7 +322,7 @@
         testScope.runTest {
             val expandable: Expandable = mock()
             secureSettingsRepository.setInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT)
-            whenever(enableZenModeDialog.createDialog()).thenReturn(mock())
+            whenever(mEnableDndDialogFactory.createDialog()).thenReturn(mock())
             collectLastValue(underTest.lockScreenState)
             runCurrent()
 
@@ -344,7 +344,7 @@
             whenever(zenModeController.isZenAvailable).thenReturn(true)
             whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF)
             settings.putInt(Settings.Secure.ZEN_DURATION, ZEN_DURATION_PROMPT)
-            whenever(enableZenModeDialog.createDialog()).thenReturn(mock())
+            whenever(mEnableDndDialogFactory.createDialog()).thenReturn(mock())
             collectLastValue(underTest.lockScreenState)
             runCurrent()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index fe9da0d..88c8b1f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -19,6 +19,7 @@
 
 import android.app.admin.DevicePolicyManager
 import android.os.UserHandle
+import android.view.accessibility.AccessibilityManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
@@ -96,6 +97,7 @@
     @Mock private lateinit var shadeInteractor: ShadeInteractor
     @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
     @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
+    @Mock private lateinit var accessibilityManager: AccessibilityManager
 
     private lateinit var underTest: KeyguardQuickAffordanceInteractor
 
@@ -199,11 +201,13 @@
                 backgroundDispatcher = kosmos.testDispatcher,
                 appContext = context,
                 communalSettingsInteractor = kosmos.communalSettingsInteractor,
+                accessibilityManager = accessibilityManager,
                 sceneInteractor = { kosmos.sceneInteractor },
             )
         kosmos.keyguardQuickAffordanceInteractor = underTest
 
         whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
+        whenever(accessibilityManager.isEnabled()).thenReturn(false)
     }
 
     @Test
@@ -672,6 +676,22 @@
         }
 
     @Test
+    fun useLongPress_withA11yEnabled_isFalse() =
+        testScope.runTest {
+            whenever(accessibilityManager.isEnabled()).thenReturn(true)
+            val useLongPress by collectLastValue(underTest.useLongPress())
+            assertThat(useLongPress).isFalse()
+        }
+
+    @Test
+    fun useLongPress_withA11yDisabled_isFalse() =
+        testScope.runTest {
+            whenever(accessibilityManager.isEnabled()).thenReturn(false)
+            val useLongPress by collectLastValue(underTest.useLongPress())
+            assertThat(useLongPress).isTrue()
+        }
+
+    @Test
     fun useLongPress_whenDocked_isFalse() =
         testScope.runTest {
             dockManager.setIsDocked(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt
index 4936f85..d782d1e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt
@@ -15,16 +15,25 @@
  */
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.ui.transitions.blurConfig
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,17 +55,33 @@
                 transitionProgress = listOf(0.0f, 0.0f, 0.3f, 0.4f, 0.5f, 1.0f),
                 startValue = kosmos.blurConfig.maxBlurRadiusPx,
                 endValue = kosmos.blurConfig.maxBlurRadiusPx,
-                transitionFactory = { value, state ->
-                    TransitionStep(
-                        from = KeyguardState.AOD,
-                        to = KeyguardState.PRIMARY_BOUNCER,
-                        value = value,
-                        transitionState = state,
-                        ownerName = "AodToPrimaryBouncerTransitionViewModelTest",
-                    )
-                },
+                transitionFactory = ::step,
                 actualValuesProvider = { values },
                 checkInterpolatedValues = false,
             )
         }
+
+    @Test
+    @EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+    fun aodToPrimaryBouncerHidesLockscreen() =
+        testScope.runTest {
+            val lockscreenAlpha by collectValues(underTest.lockscreenAlpha)
+            val notificationAlpha by collectValues(underTest.notificationAlpha)
+
+            val transitionSteps = listOf(step(0.0f, STARTED), step(0.5f), step(1.0f, FINISHED))
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(transitionSteps, testScope)
+            runCurrent()
+
+            lockscreenAlpha.forEach { assertThat(it).isEqualTo(0.0f) }
+            notificationAlpha.forEach { assertThat(it).isEqualTo(0.0f) }
+        }
+
+    private fun step(value: Float, transitionState: TransitionState = RUNNING) =
+        TransitionStep(
+            from = KeyguardState.AOD,
+            to = KeyguardState.PRIMARY_BOUNCER,
+            value = value,
+            transitionState = transitionState,
+            ownerName = "AodToPrimaryBouncerTransitionViewModelTest",
+        )
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt
index 0d48750..4d58f7a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,21 +16,26 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.ui.transitions.blurConfig
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -85,6 +90,21 @@
             )
         }
 
+    @Test
+    @EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+    fun dozingToPrimaryBouncerHidesLockscreen() =
+        testScope.runTest {
+            val lockscreenAlpha by collectValues(underTest.lockscreenAlpha)
+            val notificationAlpha by collectValues(underTest.notificationAlpha)
+
+            val transitionSteps = listOf(step(0.0f, STARTED), step(0.5f), step(1.0f, FINISHED))
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(transitionSteps, testScope)
+            runCurrent()
+
+            lockscreenAlpha.forEach { assertThat(it).isEqualTo(0.0f) }
+            notificationAlpha.forEach { assertThat(it).isEqualTo(0.0f) }
+        }
+
     private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
         return TransitionStep(
             from = KeyguardState.DOZING,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index 9173ac9..f005375 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.settings.FakeSettings
@@ -123,7 +124,12 @@
             )
 
         userActionInteractor =
-            ModesTileUserActionInteractor(inputHandler, dialogDelegate, kosmos.zenModeInteractor)
+            ModesTileUserActionInteractor(
+                inputHandler,
+                dialogDelegate,
+                kosmos.zenModeInteractor,
+                kosmos.modesDialogEventLogger,
+            )
 
         underTest =
             ModesTile(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index a063531..6a33b5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -54,7 +54,7 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        whenever(logBufferFactory.create(any(), any(), any(), any())).thenReturn(logBuffer)
+        whenever(logBufferFactory.create(any(), any(), any(), any(), any())).thenReturn(logBuffer)
         val tileSpec: TileSpec = TileSpec.create("chatty_tile")
         underTest =
             QSTileLogger(mapOf(tileSpec to chattyLogBuffer), logBufferFactory, statusBarController)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index c8b3aba..89b8e91 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,7 +61,12 @@
     private val zenModeInteractor = kosmos.zenModeInteractor
 
     private val underTest =
-        ModesTileUserActionInteractor(inputHandler, mockDialogDelegate, zenModeInteractor)
+        ModesTileUserActionInteractor(
+            inputHandler,
+            mockDialogDelegate,
+            zenModeInteractor,
+            kosmos.modesDialogEventLogger,
+        )
 
     @Test
     fun handleClick_active_showsDialog() = runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index 56a4ed0..75262a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -35,7 +36,14 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.shared.CallType
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
@@ -44,6 +52,7 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -52,6 +61,7 @@
 @RunWith(AndroidJUnit4::class)
 class CallChipViewModelTest : SysuiTestCase() {
     private val kosmos = Kosmos()
+    private val notificationListRepository = kosmos.activeNotificationListRepository
     private val testScope = kosmos.testScope
     private val repo = kosmos.ongoingCallRepository
 
@@ -65,6 +75,8 @@
                 )
                 .thenReturn(chipBackgroundView)
         }
+    private val mockExpandable: Expandable =
+        mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
 
     private val underTest by lazy { kosmos.callChipViewModel }
 
@@ -337,23 +349,25 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_inCall_nullIntent_nullClickListener() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
             repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = null))
 
-            assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull()
+            assertThat((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy).isNull()
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_inCall_positiveStartTime_validIntent_clickListenerLaunchesIntent() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
             val pendingIntent = mock<PendingIntent>()
             repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = pendingIntent))
-            val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener
+            val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListenerLegacy
             assertThat(clickListener).isNotNull()
 
             clickListener!!.onClick(chipView)
@@ -364,13 +378,15 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_inCall_zeroStartTime_validIntent_clickListenerLaunchesIntent() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
 
             val pendingIntent = mock<PendingIntent>()
             repo.setOngoingCallState(inCallModel(startTimeMs = 0, intent = pendingIntent))
-            val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener
+            val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListenerLegacy
+
             assertThat(clickListener).isNotNull()
 
             clickListener!!.onClick(chipView)
@@ -380,6 +396,72 @@
             verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null)
         }
 
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_inCall_nullIntent_noneClickBehavior() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            postOngoingCallNotification(
+                repository = notificationListRepository,
+                startTimeMs = 1000L,
+                intent = null,
+            )
+
+            assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+                .isInstanceOf(OngoingActivityChipModel.ClickBehavior.None::class.java)
+        }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_inCall_positiveStartTime_validIntent_clickBehaviorLaunchesIntent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            val pendingIntent = mock<PendingIntent>()
+            postOngoingCallNotification(
+                repository = notificationListRepository,
+                startTimeMs = 1000L,
+                intent = pendingIntent,
+            )
+
+            val clickBehavior = (latest as OngoingActivityChipModel.Shown).clickBehavior
+            assertThat(clickBehavior)
+                .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+            (clickBehavior as OngoingActivityChipModel.ClickBehavior.ExpandAction).onClick(
+                mockExpandable
+            )
+
+            // Ensure that the SysUI didn't modify the notification's intent by verifying it
+            // directly matches the `PendingIntent` set -- see b/212467440.
+            verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null)
+        }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_inCall_zeroStartTime_validIntent_clickBehaviorLaunchesIntent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            val pendingIntent = mock<PendingIntent>()
+            postOngoingCallNotification(
+                repository = notificationListRepository,
+                startTimeMs = 0L,
+                intent = pendingIntent,
+            )
+
+            val clickBehavior = (latest as OngoingActivityChipModel.Shown).clickBehavior
+            assertThat(clickBehavior)
+                .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+            (clickBehavior as OngoingActivityChipModel.ClickBehavior.ExpandAction).onClick(
+                mockExpandable
+            )
+
+            // Ensure that the SysUI didn't modify the notification's intent by verifying it
+            // directly matches the `PendingIntent` set -- see b/212467440.
+            verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null)
+        }
+
     companion object {
         fun createStatusBarIconViewOrNull(): StatusBarIconView? =
             if (StatusBarConnectedDisplays.isEnabled) {
@@ -388,6 +470,27 @@
                 mock<StatusBarIconView>()
             }
 
+        fun postOngoingCallNotification(
+            repository: ActiveNotificationListRepository,
+            startTimeMs: Long,
+            intent: PendingIntent?,
+        ) {
+            repository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply {
+                        addIndividualNotif(
+                            activeNotificationModel(
+                                key = "notif1",
+                                whenTime = startTimeMs,
+                                callType = CallType.Ongoing,
+                                statusBarChipIcon = null,
+                                contentIntent = intent,
+                            )
+                        )
+                    }
+                    .build()
+        }
+
         private val PROMOTED_CONTENT_WITH_COLOR =
             PromotedNotificationContentModel.Builder("notif")
                 .apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index c511c43..fcf8c83 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
 
 import android.content.DialogInterface
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -25,6 +26,7 @@
 import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
@@ -46,8 +48,10 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
+import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.statusbar.policy.CastDevice
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -84,6 +88,8 @@
                 )
                 .thenReturn(chipBackgroundView)
         }
+    private val mockExpandable: Expandable =
+        mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
 
     private val underTest = kosmos.castToOtherDeviceChipViewModel
 
@@ -263,7 +269,13 @@
 
             // WHEN the stop action on the dialog is clicked
             val dialogStopAction =
-                getStopActionFromDialog(latest, chipView, mockScreenCastDialog, kosmos)
+                getStopActionFromDialog(
+                    latest,
+                    chipView,
+                    mockExpandable,
+                    mockScreenCastDialog,
+                    kosmos,
+                )
             dialogStopAction.onClick(mock<DialogInterface>(), 0)
 
             // THEN the chip is immediately hidden...
@@ -296,7 +308,13 @@
 
             // WHEN the stop action on the dialog is clicked
             val dialogStopAction =
-                getStopActionFromDialog(latest, chipView, mockGenericCastDialog, kosmos)
+                getStopActionFromDialog(
+                    latest,
+                    chipView,
+                    mockExpandable,
+                    mockGenericCastDialog,
+                    kosmos,
+                )
             dialogStopAction.onClick(mock<DialogInterface>(), 0)
 
             // THEN the chip is immediately hidden...
@@ -416,13 +434,14 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_projectionStateEntireScreen_clickListenerShowsScreenCastDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             assertThat(clickListener).isNotNull()
 
             clickListener!!.onClick(chipView)
@@ -431,6 +450,7 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_projectionStateSingleTask_clickListenerShowsScreenCastDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -442,7 +462,7 @@
                     createTask(taskId = 1),
                 )
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             assertThat(clickListener).isNotNull()
 
             clickListener!!.onClick(chipView)
@@ -451,6 +471,7 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_routerStateCasting_clickListenerShowsGenericCastDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -466,7 +487,7 @@
                     )
                 )
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             assertThat(clickListener).isNotNull()
 
             clickListener!!.onClick(chipView)
@@ -480,13 +501,14 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_projectionStateCasting_clickListenerHasCuj() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             clickListener!!.onClick(chipView)
 
             val cujCaptor = argumentCaptor<DialogCuj>()
@@ -499,6 +521,7 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_routerStateCasting_clickListenerHasCuj() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -514,7 +537,7 @@
                     )
                 )
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             clickListener!!.onClick(chipView)
 
             val cujCaptor = argumentCaptor<DialogCuj>()
@@ -525,4 +548,103 @@
                 .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
             assertThat(cujCaptor.firstValue.tag).contains("Cast")
         }
+
+    @Test
+    @EnableFlags(StatusBarChipsModernization.FLAG_NAME)
+    fun chip_routerStateCasting_hasClickBehavior() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaRouterRepo.castDevices.value =
+                listOf(
+                    CastDevice(
+                        state = CastDevice.CastState.Connected,
+                        id = "id",
+                        name = "name",
+                        description = "desc",
+                        origin = CastDevice.CastOrigin.MediaRouter,
+                    )
+                )
+
+            assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+                .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+        }
+
+    @Test
+    @EnableFlags(StatusBarChipsModernization.FLAG_NAME)
+    fun chip_projectionStateCasting_hasClickBehavior() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
+
+            assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+                .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+        }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_projectionStateEntireScreen_clickBehaviorShowsScreenCastDialog() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
+
+            val expandAction =
+                ((latest as OngoingActivityChipModel.Shown).clickBehavior
+                    as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+            expandAction.onClick(mockExpandable)
+
+            verify(kosmos.mockDialogTransitionAnimator)
+                .show(eq(mockScreenCastDialog), any(), anyBoolean())
+        }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_projectionStateSingleTask_clickBehaviorShowsScreenCastDialog() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.SingleTask(
+                    CAST_TO_OTHER_DEVICES_PACKAGE,
+                    hostDeviceName = null,
+                    createTask(taskId = 1),
+                )
+
+            val expandAction =
+                ((latest as OngoingActivityChipModel.Shown).clickBehavior
+                    as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+            expandAction.onClick(mockExpandable)
+
+            verify(kosmos.mockDialogTransitionAnimator)
+                .show(eq(mockScreenCastDialog), any(), anyBoolean())
+        }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_routerStateCasting_clickBehaviorShowsGenericCastDialog() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaRouterRepo.castDevices.value =
+                listOf(
+                    CastDevice(
+                        state = CastDevice.CastState.Connected,
+                        id = "id",
+                        name = "name",
+                        description = "desc",
+                        origin = CastDevice.CastOrigin.MediaRouter,
+                    )
+                )
+
+            val expandAction =
+                ((latest as OngoingActivityChipModel.Shown).clickBehavior
+                    as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+            expandAction.onClick(mockExpandable)
+
+            verify(kosmos.mockDialogTransitionAnimator)
+                .show(eq(mockGenericCastDialog), any(), anyBoolean())
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 902db5e..eec23d3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -650,7 +650,7 @@
             )
             val chip = latest!![0]
 
-            chip.onClickListener!!.onClick(mock<View>())
+            chip.onClickListenerLegacy!!.onClick(mock<View>())
 
             assertThat(latestChipTap).isEqualTo("clickTest")
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 48d8add6..1f82dcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -17,12 +17,15 @@
 package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
 
 import android.content.DialogInterface
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.Cuj
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
@@ -41,8 +44,10 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
+import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.testKosmos
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -77,6 +82,8 @@
                 )
                 .thenReturn(chipBackgroundView)
         }
+    private val mockExpandable: Expandable =
+        mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
 
     private val underTest = kosmos.screenRecordChipViewModel
 
@@ -106,7 +113,7 @@
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Countdown::class.java)
             assertThat((latest as OngoingActivityChipModel.Shown).icon).isNull()
-            assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull()
+            assertThat((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy).isNull()
         }
 
     // The millis we typically get from [ScreenRecordRepository] are around 2995, 1995, and 995.
@@ -177,7 +184,13 @@
 
             // WHEN the stop action on the dialog is clicked
             val dialogStopAction =
-                getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+                getStopActionFromDialog(
+                    latest,
+                    chipView,
+                    mockExpandable,
+                    mockSystemUIDialog,
+                    kosmos,
+                )
             dialogStopAction.onClick(mock<DialogInterface>(), 0)
 
             // THEN both the screen record chip and the share-to-app chip are immediately hidden...
@@ -263,13 +276,14 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_notProjecting_clickListenerShowsDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
             screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
             mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             assertThat(clickListener).isNotNull()
 
             clickListener!!.onClick(chipView)
@@ -279,6 +293,7 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_projectingEntireScreen_clickListenerShowsDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -286,7 +301,7 @@
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen("host.package")
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             assertThat(clickListener).isNotNull()
 
             clickListener!!.onClick(chipView)
@@ -296,6 +311,7 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_projectingSingleTask_clickListenerShowsDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -307,7 +323,7 @@
                     FakeActivityTaskManager.createTask(taskId = 1),
                 )
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             assertThat(clickListener).isNotNull()
 
             clickListener!!.onClick(chipView)
@@ -317,22 +333,85 @@
         }
 
     @Test
-    fun chip_clickListenerHasCuj() =
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
+    fun chip_clickListenerHasCujLegacy() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
             screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen("host.package")
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             clickListener!!.onClick(chipView)
 
             val cujCaptor = argumentCaptor<DialogCuj>()
             verify(kosmos.mockDialogTransitionAnimator)
                 .showFromView(any(), any(), cujCaptor.capture(), anyBoolean())
-
             assertThat(cujCaptor.firstValue.cujType)
                 .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
             assertThat(cujCaptor.firstValue.tag).contains("Screen record")
         }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_recordingState_hasClickBehavior() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
+            assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+                .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+        }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_notProjecting_expandActionBehaviorShowsDialog() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+            mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
+
+            val expandAction =
+                ((latest as OngoingActivityChipModel.Shown).clickBehavior
+                    as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+
+            expandAction.onClick(mockExpandable)
+            verify(kosmos.mockDialogTransitionAnimator).show(any(), any(), anyBoolean())
+        }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_projectingEntireScreen_expandActionBehaviorShowsDialog() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+
+            val expandAction =
+                ((latest as OngoingActivityChipModel.Shown).clickBehavior
+                    as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+
+            expandAction.onClick(mockExpandable)
+            verify(kosmos.mockDialogTransitionAnimator).show(any(), any(), anyBoolean())
+        }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_projectingSingleTask_expandActionBehaviorShowsDialog() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.SingleTask(
+                    "host.package",
+                    hostDeviceName = null,
+                    FakeActivityTaskManager.createTask(taskId = 1),
+                )
+
+            val expandAction =
+                ((latest as OngoingActivityChipModel.Shown).clickBehavior
+                    as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+
+            expandAction.onClick(mockExpandable)
+            verify(kosmos.mockDialogTransitionAnimator).show(any(), any(), anyBoolean())
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index b3dec2e..36fc5aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
 
 import android.content.DialogInterface
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -25,6 +26,7 @@
 import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
@@ -45,8 +47,10 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
+import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
@@ -81,6 +85,8 @@
                 )
                 .thenReturn(chipBackgroundView)
         }
+    private val mockExpandable: Expandable =
+        mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
 
     private val underTest = kosmos.shareToAppChipViewModel
 
@@ -215,7 +221,13 @@
 
             // WHEN the stop action on the dialog is clicked
             val dialogStopAction =
-                getStopActionFromDialog(latest, chipView, mockScreenShareDialog, kosmos)
+                getStopActionFromDialog(
+                    latest,
+                    chipView,
+                    mockExpandable,
+                    mockScreenShareDialog,
+                    kosmos,
+                )
             dialogStopAction.onClick(mock<DialogInterface>(), 0)
 
             // THEN the chip is immediately hidden...
@@ -268,13 +280,14 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_noScreen_clickListenerShowsGenericShareDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE)
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             assertThat(clickListener).isNotNull()
 
             clickListener!!.onClick(chipView)
@@ -288,13 +301,14 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_entireScreen_clickListenerShowsScreenShareDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
             mediaProjectionRepo.mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             assertThat(clickListener).isNotNull()
 
             clickListener!!.onClick(chipView)
@@ -308,6 +322,7 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_singleTask_clickListenerShowsScreenShareDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -318,7 +333,7 @@
                     createTask(taskId = 1),
                 )
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             assertThat(clickListener).isNotNull()
 
             clickListener!!.onClick(chipView)
@@ -332,6 +347,7 @@
         }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun chip_clickListenerHasCuj() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -342,7 +358,7 @@
                     createTask(taskId = 1),
                 )
 
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
             clickListener!!.onClick(chipView)
 
             val cujCaptor = argumentCaptor<DialogCuj>()
@@ -353,4 +369,101 @@
                 .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
             assertThat(cujCaptor.firstValue.tag).contains("Share")
         }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_noScreen_hasClickBehavior() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE)
+
+            assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+                .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+        }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_entireScreen_hasClickBehavior() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+            assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+                .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+        }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_singleTask_hasClickBehavior() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.SingleTask(
+                    NORMAL_PACKAGE,
+                    hostDeviceName = null,
+                    createTask(taskId = 1),
+                )
+
+            assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+                .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+        }
+
+    @Test
+    @EnableFlags(
+        FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP,
+        StatusBarRootModernization.FLAG_NAME,
+        StatusBarChipsModernization.FLAG_NAME,
+    )
+    fun chip_noScreen_clickBehaviorShowsGenericShareDialog() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE)
+
+            val expandAction =
+                ((latest as OngoingActivityChipModel.Shown).clickBehavior
+                    as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+            expandAction.onClick(mockExpandable)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .show(eq(mockGenericShareDialog), any(), any())
+        }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_entireScreen_clickBehaviorShowsScreenShareDialog() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+            val expandAction =
+                ((latest as OngoingActivityChipModel.Shown).clickBehavior
+                    as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+            expandAction.onClick(mockExpandable)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .show(eq(mockScreenShareDialog), any(), any())
+        }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun chip_singleTask_clickBehaviorShowsScreenShareDialog() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.SingleTask(
+                    NORMAL_PACKAGE,
+                    hostDeviceName = null,
+                    createTask(taskId = 1),
+                )
+
+            val expandAction =
+                ((latest as OngoingActivityChipModel.Shown).clickBehavior
+                    as OngoingActivityChipModel.ClickBehavior.ExpandAction)
+            expandAction.onClick(mockExpandable)
+
+            verify(kosmos.mockDialogTransitionAnimator)
+                .show(eq(mockScreenShareDialog), any(), any())
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
index 8d4c68d..d099e70 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
@@ -57,7 +57,8 @@
                     icon = createIcon(R.drawable.ic_cake),
                     colors = ColorsModel.Themed,
                     startTimeMs = 100L,
-                    onClickListener = null,
+                    onClickListenerLegacy = null,
+                    clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
                 )
 
             inputChipFlow.value = newChip
@@ -68,7 +69,8 @@
                 OngoingActivityChipModel.Shown.IconOnly(
                     icon = createIcon(R.drawable.ic_hotspot),
                     colors = ColorsModel.Themed,
-                    onClickListener = null,
+                    onClickListenerLegacy = null,
+                    clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
                 )
 
             inputChipFlow.value = newerChip
@@ -89,7 +91,8 @@
                     icon = createIcon(R.drawable.ic_cake),
                     colors = ColorsModel.Themed,
                     startTimeMs = 100L,
-                    onClickListener = null,
+                    onClickListenerLegacy = null,
+                    clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
                 )
 
             inputChipFlow.value = shownChip
@@ -129,7 +132,8 @@
                     icon = createIcon(R.drawable.ic_cake),
                     colors = ColorsModel.Themed,
                     startTimeMs = 100L,
-                    onClickListener = null,
+                    onClickListenerLegacy = null,
+                    clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
                 )
 
             inputChipFlow.value = shownChip
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
index e3510f5..fc3af11 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.chips.ui.viewmodel
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -23,14 +25,19 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
+import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import kotlin.test.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.kotlin.any
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
@@ -53,8 +60,11 @@
                 )
                 .thenReturn(chipBackgroundView)
         }
+    private val mockExpandable: Expandable =
+        mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
 
     @Test
+    @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
     fun createDialogLaunchOnClickListener_showsDialogOnClick() {
         val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test")
         val clickListener =
@@ -68,11 +78,23 @@
 
         clickListener.onClick(chipView)
         verify(dialogTransitionAnimator)
-            .showFromView(
-                eq(mockSystemUIDialog),
-                eq(chipBackgroundView),
-                eq(cuj),
-                anyBoolean(),
+            .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), eq(cuj), anyBoolean())
+    }
+
+    @Test
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+    fun createDialogLaunchOnClickCallback_showsDialogOnClick() {
+        val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test")
+        val clickCallback =
+            createDialogLaunchOnClickCallback(
+                dialogDelegate,
+                dialogTransitionAnimator,
+                cuj,
+                logcatLogBuffer("OngoingActivityChipViewModelTest"),
+                "tag",
             )
+
+        clickCallback.invoke(mockExpandable)
+        verify(dialogTransitionAnimator).show(eq(mockSystemUIDialog), any(), anyBoolean())
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 42358cc..a4b6a84 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -44,6 +45,7 @@
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
@@ -91,6 +93,8 @@
                 )
                 .thenReturn(chipBackgroundView)
         }
+    private val mockExpandable: Expandable =
+        mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
 
     private val underTest = kosmos.ongoingActivityChipsViewModel
 
@@ -294,7 +298,13 @@
 
             // WHEN screen record gets stopped via dialog
             val dialogStopAction =
-                getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+                getStopActionFromDialog(
+                    latest,
+                    chipView,
+                    mockExpandable,
+                    mockSystemUIDialog,
+                    kosmos,
+                )
             dialogStopAction.onClick(mock<DialogInterface>(), 0)
 
             // THEN the chip is immediately hidden with no animation
@@ -315,7 +325,13 @@
 
             // WHEN media projection gets stopped via dialog
             val dialogStopAction =
-                getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+                getStopActionFromDialog(
+                    latest,
+                    chipView,
+                    mockExpandable,
+                    mockSystemUIDialog,
+                    kosmos,
+                )
             dialogStopAction.onClick(mock<DialogInterface>(), 0)
 
             // THEN the chip is immediately hidden with no animation
@@ -330,6 +346,7 @@
         fun getStopActionFromDialog(
             latest: OngoingActivityChipModel?,
             chipView: View,
+            expandable: Expandable,
             dialog: SystemUIDialog,
             kosmos: Kosmos,
         ): DialogInterface.OnClickListener {
@@ -349,9 +366,17 @@
                 .create(any<SystemUIDialog.Delegate>())
             whenever(kosmos.packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>()))
                 .thenThrow(PackageManager.NameNotFoundException())
-            // Click the chip so that we open the dialog and we fill in [dialogStopAction]
-            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
-            clickListener!!.onClick(chipView)
+
+            if (StatusBarChipsModernization.isEnabled) {
+                val clickBehavior = (latest as OngoingActivityChipModel.Shown).clickBehavior
+                (clickBehavior as OngoingActivityChipModel.ClickBehavior.ExpandAction).onClick(
+                    expandable
+                )
+            } else {
+                val clickListener =
+                    ((latest as OngoingActivityChipModel.Shown).onClickListenerLegacy)
+                clickListener!!.onClick(chipView)
+            }
 
             return dialogStopAction
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 0f42f29..28f3601 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -25,6 +25,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
@@ -104,6 +105,8 @@
                 )
                 .thenReturn(chipBackgroundView)
         }
+    private val mockExpandable: Expandable =
+        mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
 
     private val underTest by lazy { kosmos.ongoingActivityChipsViewModel }
 
@@ -679,7 +682,13 @@
 
             // WHEN screen record gets stopped via dialog
             val dialogStopAction =
-                getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+                getStopActionFromDialog(
+                    latest,
+                    chipView,
+                    mockExpandable,
+                    mockSystemUIDialog,
+                    kosmos,
+                )
             dialogStopAction.onClick(mock<DialogInterface>(), 0)
 
             // THEN the chip is immediately hidden with no animation
@@ -700,7 +709,13 @@
 
             // WHEN media projection gets stopped via dialog
             val dialogStopAction =
-                getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+                getStopActionFromDialog(
+                    latest,
+                    chipView,
+                    mockExpandable,
+                    mockSystemUIDialog,
+                    kosmos,
+                )
             dialogStopAction.onClick(mock<DialogInterface>(), 0)
 
             // THEN the chip is immediately hidden with no animation
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 4ef9792..0df1073 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -1383,7 +1383,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_NOTIFICATIONS_DISMISS_PRUNED_SUMMARIES)
     public void testDismissNotificationsIncludesPrunedParents() {
         // GIVEN a collection with 2 groups; one has a single child, one has two.
         mCollection.addNotificationDismissInterceptor(mInterceptor1);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
index 9227119..8d90d38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
@@ -212,7 +212,11 @@
     }
 
     @Test
-    @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    @EnableFlags(
+        PromotedNotificationUi.FLAG_NAME,
+        StatusBarNotifChips.FLAG_NAME,
+        android.app.Flags.FLAG_API_RICH_ONGOING,
+    )
     fun extractContent_fromProgressStyle() {
         val entry = createEntry {
             setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 937f333..a1c910d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
 import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -51,28 +53,15 @@
     override val shouldShowOperatorNameView = MutableStateFlow(false)
 
     override val isClockVisible =
-        MutableStateFlow(
-            HomeStatusBarViewModel.VisibilityModel(
-                visibility = View.GONE,
-                shouldAnimateChange = false,
-            )
-        )
+        MutableStateFlow(VisibilityModel(visibility = View.GONE, shouldAnimateChange = false))
 
     override val isNotificationIconContainerVisible =
-        MutableStateFlow(
-            HomeStatusBarViewModel.VisibilityModel(
-                visibility = View.GONE,
-                shouldAnimateChange = false,
-            )
-        )
+        MutableStateFlow(VisibilityModel(visibility = View.GONE, shouldAnimateChange = false))
 
     override val systemInfoCombinedVis =
         MutableStateFlow(
-            HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
-                HomeStatusBarViewModel.VisibilityModel(
-                    visibility = View.GONE,
-                    shouldAnimateChange = false,
-                ),
+            SystemInfoCombinedVisibilityModel(
+                VisibilityModel(visibility = View.GONE, shouldAnimateChange = false),
                 Idle,
             )
         )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index 03abcf8..e74d009 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -83,12 +83,11 @@
 import com.android.systemui.statusbar.phone.data.repository.fakeDarkIconRepository
 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarIconBlockList
 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarInteractorShowOperatorName
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.junit.Before
@@ -423,8 +422,9 @@
     fun areNotificationsLightsOut_requiresFlagEnabled() =
         kosmos.runTest {
             assertLogsWtf {
-                val flow = underTest.areNotificationsLightsOut
-                assertThat(flow).isEqualTo(emptyFlow<Boolean>())
+                val latest by collectLastValue(underTest.areNotificationsLightsOut)
+                // Nothing is emitted
+                assertThat(latest).isNull()
             }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
index b2378d2..2d63150 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
@@ -66,13 +66,14 @@
         whenever(
                 mockDialogTransitionAnimator.createActivityTransitionController(
                     any<SystemUIDialog>(),
-                    eq(null)
+                    eq(null),
                 )
             )
             .thenReturn(mockAnimationController)
 
         underTest =
             ModesDialogDelegate(
+                context,
                 kosmos.systemUIDialogFactory,
                 mockDialogTransitionAnimator,
                 activityStarter,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
index aa71b84..75c1742 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -43,6 +43,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskViewRepository;
 import com.android.wm.shell.taskview.TaskViewTransitions;
 import com.android.wm.shell.transition.Transitions;
 
@@ -76,6 +77,7 @@
             DragAndDropController dragAndDropController,
             ShellExecutor shellMainExecutor,
             Handler shellMainHandler,
+            TaskViewRepository taskViewRepository,
             TaskViewTransitions taskViewTransitions,
             Transitions transitions,
             SyncTransactionQueue syncQueue,
@@ -86,7 +88,7 @@
                 displayInsetsController, displayImeController, userManager, launcherApps,
                 bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, displayController,
                 oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
-                new SyncExecutor(), taskViewTransitions, transitions,
+                new SyncExecutor(), taskViewRepository, taskViewTransitions, transitions,
                 syncQueue, wmService, bubbleProperties);
         setInflateSynchronously(true);
         onInit();
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 55be9f7..ca98cbf 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -140,6 +140,11 @@
     void postStartActivityDismissingKeyguard(Intent intent, int delay,
             @Nullable ActivityTransitionAnimator.Controller animationController,
             @Nullable String customMessage);
+    /** Posts a start activity intent that dismisses keyguard. */
+    void postStartActivityDismissingKeyguard(Intent intent, int delay,
+            @Nullable ActivityTransitionAnimator.Controller animationController,
+            @Nullable String customMessage,
+            @Nullable UserHandle userHandle);
     void postStartActivityDismissingKeyguard(PendingIntent intent);
 
     /**
diff --git a/packages/SystemUI/res/layout/contextual_edu_dialog.xml b/packages/SystemUI/res/layout/contextual_edu_dialog.xml
index 09aa8da..e83d490 100644
--- a/packages/SystemUI/res/layout/contextual_edu_dialog.xml
+++ b/packages/SystemUI/res/layout/contextual_edu_dialog.xml
@@ -29,7 +29,7 @@
         android:layout_height="wrap_content"
         android:contentDescription="@null"
         android:importantForAccessibility="no"
-        android:paddingRight="16dp" />
+        android:paddingHorizontal="16dp" />
 
     <TextView
         android:id="@+id/edu_message"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 2b71c87..d363e52 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -23,7 +23,7 @@
 import android.view.MotionEvent;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
-// Next ID: 36
+// Next ID: 38
 oneway interface IOverviewProxy {
 
     void onActiveNavBarRegionChanges(in Region activeRegion) = 11;
@@ -144,4 +144,14 @@
      * TouchInteractionService is expected to send the reply once it has finished cleaning up.
      */
     void onUnbind(IRemoteCallback reply) = 35;
+
+    /**
+     * Sent when {@link TaskbarDelegate#onDisplayReady} is called.
+     */
+    void onDisplayReady(int displayId) = 36;
+
+    /**
+     * Sent when {@link TaskbarDelegate#onDisplayRemoved} is called.
+     */
+    void onDisplayRemoved(int displayId) = 37;
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 71b622a..9b852df 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -61,6 +61,8 @@
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.plugins.clocks.ZenData
 import com.android.systemui.plugins.clocks.ZenData.ZenMode
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
 import com.android.systemui.res.R as SysuiR
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.settings.UserTracker
@@ -106,6 +108,7 @@
     private val zenModeController: ZenModeController,
     private val zenModeInteractor: ZenModeInteractor,
     private val userTracker: UserTracker,
+    private val powerInteractor: PowerInteractor,
 ) {
     var loggers =
         listOf(
@@ -377,13 +380,13 @@
             override fun onTimeChanged() {
                 refreshTime()
             }
-
-            private fun refreshTime() {
-                clock?.smallClock?.events?.onTimeTick()
-                clock?.largeClock?.events?.onTimeTick()
-            }
         }
 
+    private fun refreshTime() {
+        clock?.smallClock?.events?.onTimeTick()
+        clock?.largeClock?.events?.onTimeTick()
+    }
+
     @VisibleForTesting
     internal fun listenForDnd(scope: CoroutineScope): Job {
         ModesUi.assertInNewMode()
@@ -474,6 +477,7 @@
                     listenForAnyStateToAodTransition(this)
                     listenForAnyStateToLockscreenTransition(this)
                     listenForAnyStateToDozingTransition(this)
+                    listenForScreenPowerOn(this)
                 }
             }
         smallTimeListener?.update(shouldTimeListenerRun)
@@ -643,6 +647,17 @@
         }
     }
 
+    @VisibleForTesting
+    internal fun listenForScreenPowerOn(scope: CoroutineScope): Job {
+        return scope.launch {
+            powerInteractor.screenPowerState.collect { powerState ->
+                if (powerState != ScreenPowerState.SCREEN_OFF) {
+                    refreshTime()
+                }
+            }
+        }
+    }
+
     class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
         val predrawListener =
             ViewTreeObserver.OnPreDrawListener {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index caf043a..b2f3df6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -200,7 +200,15 @@
         valueAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(@NonNull Animator animation) {
-                mHandler.post(() -> setState(ENABLED));
+                // This could be called when the animation ends or is canceled. Therefore, we need
+                // to check the state of fullscreen magnification for the following actions. We only
+                // update the state to ENABLED when the previous state is ENABLING which implies
+                // fullscreen magnification is experiencing an ongoing create border process.
+                mHandler.post(() -> {
+                    if (getState() == ENABLING) {
+                        setState(ENABLED);
+                    }
+                });
             }});
         return valueAnimator;
     }
@@ -221,7 +229,14 @@
         valueAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(@NonNull Animator animation) {
-                mHandler.post(() -> cleanUpBorder());
+                // This could be called when the animation ends or is canceled. Therefore, we need
+                // to check the state of fullscreen magnification for the following actions. Border
+                // cleanup should only happens after a removal process.
+                mHandler.post(() -> {
+                    if (getState() == DISABLING) {
+                        cleanUpBorder();
+                    }
+                });
             }});
         return valueAnimator;
     }
@@ -250,6 +265,8 @@
             // If there is an ongoing disable process or it is already disabled, return
             return;
         }
+        // The state should be updated as early as possible so others could check
+        // the ongoing process.
         setState(DISABLING);
         mShowHideBorderAnimator = createHideTargetAnimator(mFullscreenBorder);
         mShowHideBorderAnimator.start();
@@ -297,10 +314,13 @@
             // If there is an ongoing enable process or it is already enabled, return
             return;
         }
+        // The state should be updated as early as possible so others could check
+        // the ongoing process.
+        setState(ENABLING);
+
         if (mShowHideBorderAnimator != null) {
             mShowHideBorderAnimator.cancel();
         }
-        setState(ENABLING);
 
         onConfigurationChanged(mContext.getResources().getConfiguration());
         mContext.registerComponentCallbacks(this);
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
index f7ea25c..b248043 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -68,3 +68,11 @@
     setOnTouchListener(listener)
     return DisposableHandle { setOnTouchListener(null) }
 }
+
+/** A null listener should also set the longClickable property to false */
+fun View.updateLongClickListener(listener: View.OnLongClickListener?) {
+    setOnLongClickListener(listener)
+    if (listener == null) {
+        setLongClickable(false)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 1bd541e..6dc7c97 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -91,13 +91,19 @@
                 .launchIn(bgScope)
         }
 
-        // Restart the dream underneath the hub in order to support the ability to swipe
-        // away the hub to enter the dream.
-        startDream
-            .sampleFilter(powerInteractor.isAwake) { isAwake ->
-                !glanceableHubAllowKeyguardWhenDreaming() && dreamManager.canStartDreaming(isAwake)
-            }
-            .onEach { dreamManager.startDream() }
-            .launchIn(bgScope)
+        // With hub v2, we no longer need to keep the dream running underneath the hub as there is
+        // no more swipe between the hub and dream. We can just start the dream on-demand when the
+        // user presses the dream coin.
+        if (!communalSettingsInteractor.isV2FlagEnabled()) {
+            // Restart the dream underneath the hub in order to support the ability to swipe away
+            // the hub to enter the dream.
+            startDream
+                .sampleFilter(powerInteractor.isAwake) { isAwake ->
+                    !glanceableHubAllowKeyguardWhenDreaming() &&
+                        dreamManager.canStartDreaming(isAwake)
+                }
+                .onEach { dreamManager.startDream() }
+                .launchIn(bgScope)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 0b2b368..a56a63c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -562,6 +562,13 @@
             return;
         }
 
+        if (mCommunalSettingsInteractor.isV2FlagEnabled()) {
+            // Dream wake redirect is not needed in V2 as we do not need to keep the dream awake
+            // underneath the hub anymore as there is no more swipe between the dream and hub. SysUI
+            // will automatically transition to the hub when the dream wakes.
+            return;
+        }
+
         redirectWake(mCommunalAvailable && !glanceableHubAllowKeyguardWhenDreaming());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 2ed0671..e588077 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -30,6 +30,9 @@
 import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
 import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -52,6 +55,10 @@
         NotificationMinimalism.token dependsOn NotificationThrottleHun.token
         ModesEmptyShadeFix.token dependsOn modesUi
 
+        PromotedNotificationUiForceExpanded.token dependsOn PromotedNotificationUi.token
+
+        PromotedNotificationUiAod.token dependsOn PromotedNotificationUi.token
+
         // SceneContainer dependencies
         SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
index d9e55f8..d8e2dab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyboard.shortcut.ui.composable
 
+import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -27,6 +28,7 @@
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.sizeIn
@@ -211,19 +213,19 @@
             shape = RoundedCornerShape(50.dp),
             onClick = onCancel,
             color = Color.Transparent,
-            width = 80.dp,
+            modifier = Modifier.heightIn(40.dp),
             contentColor = MaterialTheme.colorScheme.primary,
             text = stringResource(R.string.shortcut_helper_customize_dialog_cancel_button_label),
+            border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outlineVariant)
         )
         Spacer(modifier = Modifier.width(8.dp))
         ShortcutHelperButton(
-            modifier =
-                Modifier.focusRequester(focusRequester).focusProperties {
-                    canFocus = true
-                }, // enable focus on touch/click mode
+            modifier = Modifier
+                .heightIn(40.dp)
+                .focusRequester(focusRequester)
+                .focusProperties { canFocus = true }, // enable focus on touch/click mode
             onClick = onConfirm,
             color = MaterialTheme.colorScheme.primary,
-            width = 116.dp,
             contentColor = MaterialTheme.colorScheme.onPrimary,
             text = confirmButtonText,
             enabled = isConfirmButtonEnabled,
@@ -413,8 +415,7 @@
 private fun ActionKeyContainer(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) {
     Row(
         modifier =
-            Modifier.height(48.dp)
-                .width(105.dp)
+            Modifier.sizeIn(minWidth = 105.dp, minHeight = 48.dp)
                 .background(
                     color = MaterialTheme.colorScheme.surface,
                     shape = RoundedCornerShape(16.dp),
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index bf60c9a5..0054dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
Binary files differ
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index 9a380f4..981a55c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -32,7 +32,6 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
@@ -45,7 +44,6 @@
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.LocalTonalElevationEnabled
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.material3.contentColorFor
 import androidx.compose.material3.minimumInteractiveComponentSize
@@ -74,13 +72,14 @@
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.compose.ui.zIndex
-import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.keyboard.shortcut.ui.model.IconSource
+import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * A selectable surface with no default focus/hover indications.
@@ -217,30 +216,37 @@
  */
 @Composable
 fun ShortcutHelperButton(
-    modifier: Modifier = Modifier,
     onClick: () -> Unit,
-    shape: Shape = RoundedCornerShape(360.dp),
+    contentColor: Color,
     color: Color,
-    width: Dp,
-    height: Dp = 40.dp,
+    modifier: Modifier = Modifier,
+    shape: Shape = RoundedCornerShape(360.dp),
     iconSource: IconSource = IconSource(),
     text: String? = null,
-    contentColor: Color,
     contentPaddingHorizontal: Dp = 16.dp,
     contentPaddingVertical: Dp = 10.dp,
     enabled: Boolean = true,
     border: BorderStroke? = null,
     contentDescription: String? = null,
 ) {
-    ShortcutHelperButtonSurface(
+    ClickableShortcutSurface(
         onClick = onClick,
         shape = shape,
-        color = color,
-        modifier = modifier,
-        enabled = enabled,
-        width = width,
-        height = height,
+        color = color.getDimmedColorIfDisabled(enabled),
         border = border,
+        modifier = modifier.semantics { role = Role.Button },
+        interactionsConfig = InteractionsConfig(
+            hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
+            hoverOverlayAlpha = 0.11f,
+            pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
+            pressedOverlayAlpha = 0.15f,
+            focusOutlineColor = MaterialTheme.colorScheme.secondary,
+            focusOutlineStrokeWidth = 3.dp,
+            focusOutlinePadding = 2.dp,
+            surfaceCornerRadius = 28.dp,
+            focusOutlineCornerRadius = 33.dp,
+        ),
+        enabled = enabled
     ) {
         Row(
             modifier =
@@ -251,76 +257,45 @@
             verticalAlignment = Alignment.CenterVertically,
             horizontalArrangement = Arrangement.Center,
         ) {
-            if (iconSource.imageVector != null) {
-                Icon(
-                    tint = contentColor,
-                    imageVector = iconSource.imageVector,
-                    contentDescription = contentDescription,
-                    modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
-                )
-            }
-
-            if (iconSource.imageVector != null && text != null)
-                Spacer(modifier = Modifier.weight(1f))
-
-            if (text != null) {
-                Text(
-                    text,
-                    color = contentColor,
-                    fontSize = 14.sp,
-                    style = MaterialTheme.typography.labelLarge,
-                    modifier = Modifier.wrapContentSize(Alignment.Center),
-                )
-            }
+            ShortcutHelperButtonContent(iconSource, contentColor, text, contentDescription)
         }
     }
 }
 
 @Composable
-private fun ShortcutHelperButtonSurface(
-    onClick: () -> Unit,
-    shape: Shape,
-    color: Color,
-    modifier: Modifier = Modifier,
-    enabled: Boolean,
-    width: Dp,
-    height: Dp,
-    border: BorderStroke?,
-    content: @Composable () -> Unit,
+private fun ShortcutHelperButtonContent(
+    iconSource: IconSource,
+    contentColor: Color,
+    text: String?,
+    contentDescription: String?
 ) {
-    if (enabled) {
-        ClickableShortcutSurface(
-            onClick = onClick,
-            shape = shape,
-            color = color,
-            border = border,
-            modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
-            interactionsConfig =
-                InteractionsConfig(
-                    hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
-                    hoverOverlayAlpha = 0.11f,
-                    pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
-                    pressedOverlayAlpha = 0.15f,
-                    focusOutlineColor = MaterialTheme.colorScheme.secondary,
-                    focusOutlineStrokeWidth = 3.dp,
-                    focusOutlinePadding = 2.dp,
-                    surfaceCornerRadius = 28.dp,
-                    focusOutlineCornerRadius = 33.dp,
-                ),
-        ) {
-            content()
-        }
-    } else {
-        Surface(
-            shape = shape,
-            color = color.copy(0.38f),
-            modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
-        ) {
-            content()
-        }
+    if (iconSource.imageVector != null) {
+        Icon(
+            tint = contentColor,
+            imageVector = iconSource.imageVector,
+            contentDescription = contentDescription,
+            modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
+        )
+    }
+
+    if (iconSource.imageVector != null && text != null)
+        Spacer(modifier = Modifier.width(8.dp))
+
+    if (text != null) {
+        Text(
+            text,
+            color = contentColor,
+            fontSize = 14.sp,
+            style = MaterialTheme.typography.labelLarge,
+            modifier = Modifier.wrapContentSize(Alignment.Center),
+            overflow = TextOverflow.Ellipsis,
+        )
     }
 }
 
+private fun Color.getDimmedColorIfDisabled(enabled: Boolean): Color =
+    if (enabled) this else copy(alpha = 0.38f)
+
 @Composable
 private fun surfaceColorAtElevation(color: Color, elevation: Dp): Color {
     return MaterialTheme.colorScheme.applyTonalElevation(color, elevation)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index df41535..5869274 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard
 
 import android.app.IActivityTaskManager
+import android.os.RemoteException
 import android.util.Log
 import android.view.IRemoteAnimationFinishedCallback
 import android.view.RemoteAnimationTarget
@@ -265,7 +266,11 @@
         if (enableNewKeyguardShellTransitions) {
             startKeyguardTransition(lockscreenShowing, aodVisible)
         } else {
-            activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
+            try {
+                activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
+            } catch (e: RemoteException) {
+                Log.e(TAG, "Remote exception", e)
+            }
         }
         this.isLockscreenShowing = lockscreenShowing
         this.isAodVisible = aodVisible
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index 1b8baf6..f11ebee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -26,8 +26,8 @@
 import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
 import android.service.notification.ZenModeConfig
 import android.util.Log
-import com.android.settingslib.notification.modes.EnableZenModeDialog
-import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
+import com.android.settingslib.notification.modes.EnableDndDialogFactory
+import com.android.settingslib.notification.modes.EnableDndDialogMetricsLogger
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -60,8 +60,7 @@
 import kotlinx.coroutines.flow.stateIn
 
 @SysUISingleton
-class DoNotDisturbQuickAffordanceConfig
-constructor(
+class DoNotDisturbQuickAffordanceConfig(
     private val context: Context,
     private val controller: ZenModeController,
     private val interactor: ZenModeInteractor,
@@ -70,7 +69,7 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     @Background private val backgroundScope: CoroutineScope,
     private val testConditionId: Uri?,
-    testDialog: EnableZenModeDialog?,
+    testDialogFactory: EnableDndDialogFactory?,
 ) : KeyguardQuickAffordanceConfig {
 
     @Inject
@@ -118,13 +117,13 @@
                     )
                     .id
 
-    private val dialog: EnableZenModeDialog by lazy {
-        testDialog
-            ?: EnableZenModeDialog(
+    private val dialogFactory: EnableDndDialogFactory by lazy {
+        testDialogFactory
+            ?: EnableDndDialogFactory(
                 context,
                 R.style.Theme_SystemUI_Dialog,
                 true, /* cancelIsNeutral */
-                ZenModeDialogMetricsLogger(context),
+                EnableDndDialogMetricsLogger(context),
             )
     }
 
@@ -224,7 +223,7 @@
                     if (interactor.shouldAskForZenDuration(dnd)) {
                         // NOTE: The dialog handles turning on the mode itself.
                         return KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
-                            dialog.createDialog(),
+                            dialogFactory.createDialog(),
                             expandable,
                         )
                     } else {
@@ -243,7 +242,7 @@
 
                 settingsValue == ZEN_DURATION_PROMPT ->
                     KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
-                        dialog.createDialog(),
+                        dialogFactory.createDialog(),
                         expandable,
                     )
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 7d8badd..b866fca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -22,6 +22,7 @@
 import android.content.Context
 import android.content.Intent
 import android.util.Log
+import android.view.accessibility.AccessibilityManager
 import com.android.app.tracing.coroutines.withContextTraced as withContext
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.internal.widget.LockPatternUtils
@@ -92,6 +93,7 @@
     private val dockManager: DockManager,
     private val biometricSettingsRepository: BiometricSettingsRepository,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
+    private val accessibilityManager: AccessibilityManager,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     @ShadeDisplayAware private val appContext: Context,
     private val sceneInteractor: Lazy<SceneInteractor>,
@@ -115,7 +117,10 @@
      *
      * If `false`, the UI goes back to using single taps.
      */
-    fun useLongPress(): Flow<Boolean> = dockManager.retrieveIsDocked().map { !it }
+    fun useLongPress(): Flow<Boolean> =
+        dockManager.retrieveIsDocked().map { isDocked ->
+            !isDocked && !accessibilityManager.isEnabled()
+        }
 
     /** Returns an observable for the quick affordance at the given position. */
     suspend fun quickAffordance(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 8a2e3dd..f396cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.common.ui.view.updateLongClickListener
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
@@ -275,6 +276,7 @@
                     )
             } else {
                 view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
+                view.updateLongClickListener(null)
             }
         } else {
             view.onLongClickListener = null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
index e3b5587..26bf0bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
@@ -29,6 +30,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
 
 /**
  * Breaks down AOD->PRIMARY BOUNCER transition into discrete steps for corresponding views to
@@ -54,6 +56,12 @@
     override val windowBlurRadius: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
 
+    val lockscreenAlpha: Flow<Float> =
+        if (Flags.bouncerUiRevamp()) transitionAnimation.immediatelyTransitionTo(0.0f)
+        else emptyFlow()
+
+    val notificationAlpha = lockscreenAlpha
+
     override val notificationBlurRadius: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0.0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
index c937d5c..d9ca267 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_PRIMARY_BOUNCER_DURATION
 import com.android.systemui.keyguard.shared.model.Edge
@@ -29,6 +30,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
 
 /**
  * Breaks down DOZING->PRIMARY BOUNCER transition into discrete steps for corresponding views to
@@ -64,6 +66,13 @@
             },
             onFinish = { blurConfig.maxBlurRadiusPx },
         )
+
+    val lockscreenAlpha: Flow<Float> =
+        if (Flags.bouncerUiRevamp()) transitionAnimation.immediatelyTransitionTo(0.0f)
+        else emptyFlow()
+
+    val notificationAlpha: Flow<Float> = lockscreenAlpha
+
     override val notificationBlurRadius: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index eaba5d5..e51e05b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -96,9 +96,12 @@
     private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
     private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
     private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
+    private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel,
     private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
     private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
     private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
+    private val dozingToPrimaryBouncerTransitionViewModel:
+        DozingToPrimaryBouncerTransitionViewModel,
     private val dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel,
     private val dreamingToGoneTransitionViewModel: DreamingToGoneTransitionViewModel,
     private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
@@ -243,9 +246,11 @@
                         aodToGoneTransitionViewModel.lockscreenAlpha(viewState),
                         aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
                         aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+                        aodToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
                         dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
                         dozingToLockscreenTransitionViewModel.lockscreenAlpha,
                         dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+                        dozingToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
                         dreamingToAodTransitionViewModel.lockscreenAlpha,
                         dreamingToGoneTransitionViewModel.lockscreenAlpha,
                         dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index 6351d7d..c9d6f81 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer.Companion.DEFAULT_LOGBUFFER_TRACK_NAME
 import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
 import com.android.systemui.log.echo.LogcatEchoTrackerAlways
 import javax.inject.Inject
@@ -27,7 +28,7 @@
 @Inject
 constructor(
     private val dumpManager: DumpManager,
-    private val logcatEchoTracker: LogcatEchoTracker
+    private val logcatEchoTracker: LogcatEchoTracker,
 ) {
     @JvmOverloads
     fun create(
@@ -35,9 +36,11 @@
         maxSize: Int,
         systrace: Boolean = true,
         alwaysLogToLogcat: Boolean = false,
+        systraceTrackName: String = DEFAULT_LOGBUFFER_TRACK_NAME,
     ): LogBuffer {
         val echoTracker = if (alwaysLogToLogcat) LogcatEchoTrackerAlways else logcatEchoTracker
-        val buffer = LogBuffer(name, adjustMaxSize(maxSize), echoTracker, systrace)
+        val buffer =
+            LogBuffer(name, adjustMaxSize(maxSize), echoTracker, systrace, systraceTrackName)
         dumpManager.registerBuffer(name, buffer)
         return buffer
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 2191f37..f1f299a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -45,16 +45,15 @@
 import android.media.session.MediaSession
 import android.media.session.PlaybackState
 import android.net.Uri
-import android.os.Parcelable
 import android.os.Process
 import android.os.UserHandle
-import android.provider.Settings
 import android.service.notification.StatusBarNotification
 import android.support.v4.media.MediaMetadataCompat
 import android.text.TextUtils
 import android.util.Log
 import android.util.Pair as APair
 import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.tracing.traceSection
 import com.android.internal.annotations.Keep
 import com.android.internal.logging.InstanceId
@@ -86,11 +85,9 @@
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.media.controls.util.SmallHash
-import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
-import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.Assert
 import com.android.systemui.util.Utils
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -103,7 +100,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 // URI fields to try loading album art from
@@ -152,22 +148,6 @@
         expiryTimeMs = 0,
     )
 
-const val MEDIA_TITLE_ERROR_MESSAGE = "Invalid media data: title is null or blank."
-
-/**
- * Allow recommendations from smartspace to show in media controls. Requires
- * [Utils.useQsMediaPlayer] to be enabled. On by default, but can be disabled by setting to 0
- */
-private fun allowMediaRecommendations(context: Context): Boolean {
-    val flag =
-        Settings.Secure.getInt(
-            context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
-            1,
-        )
-    return Utils.useQsMediaPlayer(context) && flag > 0
-}
-
 /** A class that facilitates management and loading of Media Data, ready for binding. */
 @SysUISingleton
 class LegacyMediaDataManagerImpl(
@@ -191,14 +171,13 @@
     private var useMediaResumption: Boolean,
     private val useQsMediaPlayer: Boolean,
     private val systemClock: SystemClock,
-    private val tunerService: TunerService,
     private val mediaFlags: MediaFlags,
     private val logger: MediaUiEventLogger,
     private val smartspaceManager: SmartspaceManager?,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
     private val mediaLogger: MediaLogger,
-) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener, MediaDataManager {
+) : Dumpable, MediaDataManager {
 
     companion object {
         // UI surface label for subscribing Smartspace updates.
@@ -238,7 +217,6 @@
     // There should ONLY be at most one Smartspace media recommendation.
     var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
     @Keep private var smartspaceSession: SmartspaceSession? = null
-    private var allowMediaRecommendations = allowMediaRecommendations(context)
 
     private val artworkWidth =
         context.resources.getDimensionPixelSize(
@@ -276,7 +254,6 @@
         mediaDataFilter: LegacyMediaDataFilterImpl,
         smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
         clock: SystemClock,
-        tunerService: TunerService,
         mediaFlags: MediaFlags,
         logger: MediaUiEventLogger,
         smartspaceManager: SmartspaceManager?,
@@ -306,7 +283,6 @@
         Utils.useMediaResumption(context),
         Utils.useQsMediaPlayer(context),
         clock,
-        tunerService,
         mediaFlags,
         logger,
         smartspaceManager,
@@ -372,7 +348,7 @@
         context.registerReceiver(appChangeReceiver, uninstallFilter)
 
         // Register for Smartspace data updates.
-        smartspaceMediaDataProvider.registerListener(this)
+        // TODO(b/382680767): remove
         smartspaceSession =
             smartspaceManager?.createSmartspaceSession(
                 SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
@@ -391,24 +367,9 @@
             )
         }
         smartspaceSession?.let { it.requestSmartspaceUpdate() }
-        tunerService.addTunable(
-            object : TunerService.Tunable {
-                override fun onTuningChanged(key: String?, newValue: String?) {
-                    allowMediaRecommendations = allowMediaRecommendations(context)
-                    if (!allowMediaRecommendations) {
-                        dismissSmartspaceRecommendation(
-                            key = smartspaceMediaData.targetId,
-                            delay = 0L,
-                        )
-                    }
-                }
-            },
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
-        )
     }
 
     override fun destroy() {
-        smartspaceMediaDataProvider.unregisterListener(this)
         smartspaceSession?.close()
         smartspaceSession = null
         context.unregisterReceiver(appChangeReceiver)
@@ -1328,61 +1289,6 @@
             }
         }
 
-    override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) {
-        if (!allowMediaRecommendations) {
-            if (DEBUG) Log.d(TAG, "Smartspace recommendation is disabled in Settings.")
-            return
-        }
-
-        val mediaTargets = targets.filterIsInstance<SmartspaceTarget>()
-        when (mediaTargets.size) {
-            0 -> {
-                if (!smartspaceMediaData.isActive) {
-                    return
-                }
-                if (DEBUG) {
-                    Log.d(TAG, "Set Smartspace media to be inactive for the data update")
-                }
-                if (mediaFlags.isPersistentSsCardEnabled()) {
-                    // Smartspace uses this signal to hide the card (e.g. when it expires or user
-                    // disconnects headphones), so treat as setting inactive when flag is on
-                    smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
-                    notifySmartspaceMediaDataLoaded(
-                        smartspaceMediaData.targetId,
-                        smartspaceMediaData,
-                    )
-                } else {
-                    smartspaceMediaData =
-                        EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-                            targetId = smartspaceMediaData.targetId,
-                            instanceId = smartspaceMediaData.instanceId,
-                        )
-                    notifySmartspaceMediaDataRemoved(
-                        smartspaceMediaData.targetId,
-                        immediately = false,
-                    )
-                }
-            }
-            1 -> {
-                val newMediaTarget = mediaTargets.get(0)
-                if (smartspaceMediaData.targetId == newMediaTarget.smartspaceTargetId) {
-                    // The same Smartspace updates can be received. Skip the duplicate updates.
-                    return
-                }
-                if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
-                smartspaceMediaData = toSmartspaceMediaData(newMediaTarget)
-                notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
-            }
-            else -> {
-                // There should NOT be more than 1 Smartspace media update. When it happens, it
-                // indicates a bad state or an error. Reset the status accordingly.
-                Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
-                notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
-                smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
-            }
-        }
-    }
-
     override fun onNotificationRemoved(key: String) {
         Assert.isMainThread()
         val removed = mediaEntries.remove(key) ?: return
@@ -1641,7 +1547,6 @@
             println("externalListeners: ${mediaDataFilter.listeners}")
             println("mediaEntries: $mediaEntries")
             println("useMediaResumption: $useMediaResumption")
-            println("allowMediaRecommendations: $allowMediaRecommendations")
         }
         mediaDeviceManager.dump(pw)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 3821f3d..a524db4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -45,16 +45,15 @@
 import android.media.session.MediaSession
 import android.media.session.PlaybackState
 import android.net.Uri
-import android.os.Parcelable
 import android.os.Process
 import android.os.UserHandle
-import android.provider.Settings
 import android.service.notification.StatusBarNotification
 import android.support.v4.media.MediaMetadataCompat
 import android.text.TextUtils
 import android.util.Log
 import android.util.Pair as APair
 import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.tracing.traceSection
 import com.android.internal.annotations.Keep
 import com.android.internal.logging.InstanceId
@@ -87,8 +86,6 @@
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.media.controls.util.SmallHash
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
@@ -97,8 +94,6 @@
 import com.android.systemui.util.Utils
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.ThreadFactory
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import com.android.systemui.util.time.SystemClock
 import java.io.IOException
 import java.io.PrintWriter
@@ -106,12 +101,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 // URI fields to try loading album art from
@@ -139,12 +128,10 @@
     private val mediaControllerFactory: MediaControllerFactory,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val dumpManager: DumpManager,
-    private val activityStarter: ActivityStarter,
     private val smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
     private var useMediaResumption: Boolean,
     private val useQsMediaPlayer: Boolean,
     private val systemClock: SystemClock,
-    private val secureSettings: SecureSettings,
     private val mediaFlags: MediaFlags,
     private val logger: MediaUiEventLogger,
     private val smartspaceManager: SmartspaceManager?,
@@ -152,7 +139,7 @@
     private val mediaDataRepository: MediaDataRepository,
     private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
     private val mediaLogger: MediaLogger,
-) : CoreStartable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
+) : CoreStartable {
 
     companion object {
         /**
@@ -191,7 +178,6 @@
 
     // There should ONLY be at most one Smartspace media recommendation.
     @Keep private var smartspaceSession: SmartspaceSession? = null
-    private var allowMediaRecommendations = false
 
     private val artworkWidth =
         context.resources.getDimensionPixelSize(
@@ -221,10 +207,8 @@
         mediaControllerFactory: MediaControllerFactory,
         dumpManager: DumpManager,
         broadcastDispatcher: BroadcastDispatcher,
-        activityStarter: ActivityStarter,
         smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
         clock: SystemClock,
-        secureSettings: SecureSettings,
         mediaFlags: MediaFlags,
         logger: MediaUiEventLogger,
         smartspaceManager: SmartspaceManager?,
@@ -245,12 +229,10 @@
         mediaControllerFactory,
         broadcastDispatcher,
         dumpManager,
-        activityStarter,
         smartspaceMediaDataProvider,
         Utils.useMediaResumption(context),
         Utils.useQsMediaPlayer(context),
         clock,
-        secureSettings,
         mediaFlags,
         logger,
         smartspaceManager,
@@ -296,7 +278,7 @@
         context.registerReceiver(appChangeReceiver, uninstallFilter)
 
         // Register for Smartspace data updates.
-        smartspaceMediaDataProvider.registerListener(this)
+        // TODO(b/382680767): remove
         smartspaceSession =
             smartspaceManager?.createSmartspaceSession(
                 SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
@@ -314,13 +296,9 @@
             }
         }
         smartspaceSession?.requestSmartspaceUpdate()
-
-        // Track media controls recommendation setting.
-        applicationScope.launch { trackMediaControlsRecommendationSetting() }
     }
 
     fun destroy() {
-        smartspaceMediaDataProvider.unregisterListener(this)
         smartspaceSession?.close()
         smartspaceSession = null
         context.unregisterReceiver(appChangeReceiver)
@@ -357,43 +335,6 @@
         }
     }
 
-    /**
-     * Allow recommendations from smartspace to show in media controls. Requires
-     * [Utils.useQsMediaPlayer] to be enabled. On by default, but can be disabled by setting to 0
-     */
-    private suspend fun allowMediaRecommendations(): Boolean {
-        return withContext(backgroundDispatcher) {
-            val flag =
-                secureSettings.getBoolForUser(
-                    Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
-                    true,
-                    UserHandle.USER_CURRENT,
-                )
-
-            useQsMediaPlayer && flag
-        }
-    }
-
-    private suspend fun trackMediaControlsRecommendationSetting() {
-        secureSettings
-            .observerFlow(UserHandle.USER_ALL, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)
-            // perform a query at the beginning.
-            .onStart { emit(Unit) }
-            .map { allowMediaRecommendations() }
-            .distinctUntilChanged()
-            .flowOn(backgroundDispatcher)
-            // only track the most recent emission
-            .collectLatest {
-                allowMediaRecommendations = it
-                if (!allowMediaRecommendations) {
-                    dismissSmartspaceRecommendation(
-                        key = mediaDataRepository.smartspaceMediaData.value.targetId,
-                        delay = 0L,
-                    )
-                }
-            }
-    }
-
     private fun removeAllForPackage(packageName: String) {
         Assert.isMainThread()
         val toRemove =
@@ -1277,62 +1218,6 @@
             }
         }
 
-    override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) {
-        if (!allowMediaRecommendations) {
-            if (DEBUG) Log.d(TAG, "Smartspace recommendation is disabled in Settings.")
-            return
-        }
-
-        val mediaTargets = targets.filterIsInstance<SmartspaceTarget>()
-        val smartspaceMediaData = mediaDataRepository.smartspaceMediaData.value
-        when (mediaTargets.size) {
-            0 -> {
-                if (!smartspaceMediaData.isActive) {
-                    return
-                }
-                if (DEBUG) {
-                    Log.d(TAG, "Set Smartspace media to be inactive for the data update")
-                }
-                if (mediaFlags.isPersistentSsCardEnabled()) {
-                    // Smartspace uses this signal to hide the card (e.g. when it expires or user
-                    // disconnects headphones), so treat as setting inactive when flag is on
-                    val recommendation = smartspaceMediaData.copy(isActive = false)
-                    mediaDataRepository.setRecommendation(recommendation)
-                    notifySmartspaceMediaDataLoaded(recommendation.targetId, recommendation)
-                } else {
-                    notifySmartspaceMediaDataRemoved(
-                        smartspaceMediaData.targetId,
-                        immediately = false,
-                    )
-                    mediaDataRepository.setRecommendation(
-                        SmartspaceMediaData(
-                            targetId = smartspaceMediaData.targetId,
-                            instanceId = smartspaceMediaData.instanceId,
-                        )
-                    )
-                }
-            }
-            1 -> {
-                val newMediaTarget = mediaTargets.get(0)
-                if (smartspaceMediaData.targetId == newMediaTarget.smartspaceTargetId) {
-                    // The same Smartspace updates can be received. Skip the duplicate updates.
-                    return
-                }
-                if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
-                val recommendation = toSmartspaceMediaData(newMediaTarget)
-                mediaDataRepository.setRecommendation(recommendation)
-                notifySmartspaceMediaDataLoaded(recommendation.targetId, recommendation)
-            }
-            else -> {
-                // There should NOT be more than 1 Smartspace media update. When it happens, it
-                // indicates a bad state or an error. Reset the status accordingly.
-                Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
-                notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
-                mediaDataRepository.setRecommendation(SmartspaceMediaData())
-            }
-        }
-    }
-
     fun onNotificationRemoved(key: String) {
         Assert.isMainThread()
         val removed = mediaDataRepository.removeMediaEntry(key) ?: return
@@ -1621,7 +1506,6 @@
         pw.apply {
             println("internalListeners: $internalListeners")
             println("useMediaResumption: $useMediaResumption")
-            println("allowMediaRecommendations: $allowMediaRecommendations")
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 86e9294..975f8f4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -1042,6 +1042,18 @@
             return null
         }
 
+        if (state.expansion == 1.0f) {
+            val height =
+                if (state.expandedMatchesParentHeight) {
+                    heightInSceneContainerPx
+                } else {
+                    context.resources.getDimensionPixelSize(
+                        R.dimen.qs_media_session_height_expanded
+                    )
+                }
+            setBackgroundHeights(height)
+        }
+
         // Similar to obtainViewState: Let's create a new measurement
         val result =
             transitionLayout?.calculateViewState(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index d94424c..e19d6e9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -57,6 +57,7 @@
             // activity and a null second task, so the foreground task will be index 1, but when
             // opening the app selector in split screen mode, the foreground task will be the second
             // task in index 0.
+            // TODO(346588978): This needs to be updated for mixed groups
             val foregroundGroup =
                 if (groupedTasks.firstOrNull()?.splitBounds != null) groupedTasks.first()
                 else groupedTasks.elementAtOrNull(1)
@@ -69,7 +70,7 @@
                         it.taskInfo1,
                         it.taskInfo1.taskId in foregroundTaskIds && it.taskInfo1.isVisible,
                         userManager.getUserInfo(it.taskInfo1.userId).toUserType(),
-                        it.splitBounds
+                        it.splitBounds,
                     )
 
                 val task2 =
@@ -78,7 +79,7 @@
                             it.taskInfo2!!,
                             it.taskInfo2!!.taskId in foregroundTaskIds && it.taskInfo2!!.isVisible,
                             userManager.getUserInfo(it.taskInfo2!!.userId).toUserType(),
-                            it.splitBounds
+                            it.splitBounds,
                         )
                     } else null
 
@@ -92,7 +93,7 @@
                 Integer.MAX_VALUE,
                 RECENT_IGNORE_UNAVAILABLE,
                 userTracker.userId,
-                backgroundExecutor
+                backgroundExecutor,
             ) { tasks ->
                 continuation.resume(tasks)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 3f14b55e..9270fff6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -236,11 +236,29 @@
     @Override
     public void onDisplayReady(int displayId) {
         CommandQueue.Callbacks.super.onDisplayReady(displayId);
+        if (mOverviewProxyService.getProxy() == null) {
+            return;
+        }
+
+        try {
+            mOverviewProxyService.getProxy().onDisplayReady(displayId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "onDisplayReady() failed", e);
+        }
     }
 
     @Override
     public void onDisplayRemoved(int displayId) {
         CommandQueue.Callbacks.super.onDisplayRemoved(displayId);
+        if (mOverviewProxyService.getProxy() == null) {
+            return;
+        }
+
+        try {
+            mOverviewProxyService.getProxy().onDisplayRemoved(displayId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "onDisplayRemoved() failed", e);
+        }
     }
 
     // Separated into a method to keep setDependencies() clean/readable.
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
index f53b6cd..57d4063 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
@@ -39,7 +39,6 @@
 import androidx.annotation.WorkerThread
 import androidx.core.view.ViewCompat
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
-import com.android.settingslib.Utils
 import com.android.systemui.animation.ViewHierarchyAnimator
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -63,7 +62,7 @@
     private val list: List<PrivacyElement>,
     private val manageApp: (String, Int, Intent) -> Unit,
     private val closeApp: (String, Int) -> Unit,
-    private val openPrivacyDashboard: () -> Unit
+    private val openPrivacyDashboard: () -> Unit,
 ) : SystemUIDialog(context, R.style.Theme_PrivacyDialog) {
 
     private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>()
@@ -192,11 +191,9 @@
             return null
         }
         val closeAppButton =
-            checkNotNull(window).layoutInflater.inflate(
-                R.layout.privacy_dialog_card_button,
-                expandedLayout,
-                false
-            ) as Button
+            checkNotNull(window)
+                .layoutInflater
+                .inflate(R.layout.privacy_dialog_card_button, expandedLayout, false) as Button
         expandedLayout.addView(closeAppButton)
         closeAppButton.id = R.id.privacy_dialog_close_app_button
         closeAppButton.setText(R.string.privacy_dialog_close_app_button)
@@ -248,11 +245,9 @@
 
     private fun configureManageButton(element: PrivacyElement, expandedLayout: ViewGroup): View {
         val manageButton =
-            checkNotNull(window).layoutInflater.inflate(
-                R.layout.privacy_dialog_card_button,
-                expandedLayout,
-                false
-            ) as Button
+            checkNotNull(window)
+                .layoutInflater
+                .inflate(R.layout.privacy_dialog_card_button, expandedLayout, false) as Button
         expandedLayout.addView(manageButton)
         manageButton.id = R.id.privacy_dialog_manage_app_button
         manageButton.setText(
@@ -294,7 +289,7 @@
             itemCard,
             AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
             context.getString(R.string.privacy_dialog_expand_action),
-            null
+            null,
         )
 
         val expandedLayout =
@@ -311,7 +306,7 @@
                     it!!,
                     AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
                     context.getString(R.string.privacy_dialog_expand_action),
-                    null
+                    null,
                 )
             } else {
                 expandedLayout.visibility = View.VISIBLE
@@ -320,12 +315,12 @@
                     it!!,
                     AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
                     context.getString(R.string.privacy_dialog_collapse_action),
-                    null
+                    null,
                 )
             }
             ViewHierarchyAnimator.animateNextUpdate(
                 rootView = window!!.decorView,
-                excludedViews = setOf(expandedLayout)
+                excludedViews = setOf(expandedLayout),
             )
         }
     }
@@ -345,19 +340,13 @@
 
     @ColorInt
     private fun getForegroundColor(active: Boolean) =
-        Utils.getColorAttrDefaultColor(
-            context,
-            if (active) com.android.internal.R.color.materialColorOnPrimaryFixed
-            else com.android.internal.R.color.materialColorOnSurface,
-        )
+        if (active) context.getColor(com.android.internal.R.color.materialColorOnPrimaryFixed)
+        else context.getColor(com.android.internal.R.color.materialColorOnSurface)
 
     @ColorInt
     private fun getBackgroundColor(active: Boolean) =
-        Utils.getColorAttrDefaultColor(
-            context,
-            if (active) com.android.internal.R.color.materialColorPrimaryFixed
-            else com.android.internal.R.color.materialColorSurfaceContainerHigh,
-        )
+        if (active) context.getColor(com.android.internal.R.color.materialColorPrimaryFixed)
+        else context.getColor(com.android.internal.R.color.materialColorSurfaceContainerHigh)
 
     private fun getMutableDrawable(@DrawableRes resId: Int) = context.getDrawable(resId)!!.mutate()
 
@@ -379,7 +368,7 @@
             context.getString(
                 singleUsageResId,
                 element.applicationName,
-                element.attributionLabel ?: element.proxyLabel
+                element.attributionLabel ?: element.proxyLabel,
             )
         } else {
             val doubleUsageResId: Int =
@@ -389,7 +378,7 @@
                 doubleUsageResId,
                 element.applicationName,
                 element.attributionLabel,
-                element.proxyLabel
+                element.proxyLabel,
             )
         }
 
@@ -429,7 +418,7 @@
             return groupInfo.loadSafeLabel(
                 this,
                 0f,
-                TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM
+                TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM,
             )
         }
 
@@ -472,7 +461,7 @@
             icon: Drawable,
             iconSize: Int,
             background: Drawable,
-            backgroundSize: Int
+            backgroundSize: Int,
         ): Drawable {
             val layered = LayerDrawable(arrayOf(background, icon))
             layered.setLayerSize(0, backgroundSize, backgroundSize)
@@ -497,7 +486,7 @@
         val isPhoneCall: Boolean,
         val isService: Boolean,
         val permGroupName: String,
-        val navigationIntent: Intent
+        val navigationIntent: Intent,
     ) {
         private val builder = StringBuilder("PrivacyElement(")
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index b53685e..6d90784 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -57,7 +57,6 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
@@ -95,6 +94,7 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
 import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.transitions
 import com.android.compose.modifiers.height
 import com.android.compose.modifiers.padding
@@ -313,8 +313,8 @@
      */
     @Composable
     private fun CollapsableQuickSettingsSTL() {
-        val sceneState = remember {
-            MutableSceneTransitionLayoutState(
+        val sceneState =
+            rememberMutableSceneTransitionLayoutState(
                 viewModel.expansionState.toIdleSceneKey(),
                 transitions =
                     transitions {
@@ -323,7 +323,6 @@
                         }
                     },
             )
-        }
 
         LaunchedEffect(Unit) {
             synchronizeQsState(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 04f0b87..1e8ef35 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -42,7 +42,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.notification.modes.EnableZenModeDialog;
+import com.android.settingslib.notification.modes.EnableDndDialogFactory;
 import com.android.systemui.Prefs;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogTransitionAnimator;
@@ -59,7 +59,7 @@
 import com.android.systemui.qs.UserSettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger;
+import com.android.systemui.qs.tiles.dialog.QSEnableDndDialogMetricsLogger;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -84,7 +84,7 @@
     private final SharedPreferences mSharedPreferences;
     private final UserSettingObserver mSettingZenDuration;
     private final DialogTransitionAnimator mDialogTransitionAnimator;
-    private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger;
+    private final QSEnableDndDialogMetricsLogger mQSDndDurationDialogLogger;
 
     private boolean mListening;
 
@@ -121,7 +121,7 @@
                 refreshState();
             }
         };
-        mQSZenDialogMetricsLogger = new QSZenModeDialogMetricsLogger(mContext);
+        mQSDndDurationDialogLogger = new QSEnableDndDialogMetricsLogger(mContext);
     }
 
     public static void setVisible(Context context, boolean visible) {
@@ -201,9 +201,9 @@
     }
 
     private Dialog makeZenModeDialog() {
-        AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog,
+        AlertDialog dialog = new EnableDndDialogFactory(mContext, R.style.Theme_SystemUI_Dialog,
                 true /* cancelIsNeutral */,
-                mQSZenDialogMetricsLogger).createDialog();
+                mQSDndDurationDialogLogger).createDialog();
         SystemUIDialog.applyFlags(dialog);
         SystemUIDialog.setShowForAllUsers(dialog, true);
         SystemUIDialog.registerDismissListener(dialog);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSEnableDndDialogMetricsLogger.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java
rename to packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSEnableDndDialogMetricsLogger.java
index b3f66a6..5196a22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSEnableDndDialogMetricsLogger.java
@@ -19,7 +19,7 @@
 import android.content.Context;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger;
+import com.android.settingslib.notification.modes.EnableDndDialogMetricsLogger;
 import com.android.systemui.qs.QSDndEvent;
 import com.android.systemui.qs.QSEvents;
 
@@ -30,10 +30,10 @@
  *
  * Other names for DND (Do Not Disturb) include "Zen" and "Priority only".
  */
-public class QSZenModeDialogMetricsLogger extends ZenModeDialogMetricsLogger {
+public class QSEnableDndDialogMetricsLogger extends EnableDndDialogMetricsLogger {
     private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
 
-    public QSZenModeDialogMetricsLogger(Context context) {
+    public QSEnableDndDialogMetricsLogger(Context context) {
         super(context);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
index 594394f..5ce7f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger
 import javax.inject.Inject
 
 @SysUISingleton
@@ -39,6 +40,7 @@
     // TODO(b/353896370): The domain layer should not have to depend on the UI layer.
     private val dialogDelegate: ModesDialogDelegate,
     private val zenModeInteractor: ZenModeInteractor,
+    private val dialogEventLogger: ModesDialogEventLogger,
 ) : QSTileUserActionInteractor<ModesTileModel> {
     val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
 
@@ -78,7 +80,16 @@
                 Log.wtf(TAG, "Triggered DND but it's null!?")
                 return
             }
-            zenModeInteractor.activateMode(dnd)
+
+            if (zenModeInteractor.shouldAskForZenDuration(dnd)) {
+                dialogEventLogger.logOpenDurationDialog(dnd)
+                // NOTE: The dialog handles turning on the mode itself.
+                val dialog = dialogDelegate.makeDndDurationDialog()
+                dialog.show()
+            } else {
+                dialogEventLogger.logModeOn(dnd)
+                zenModeInteractor.activateMode(dnd)
+            }
         } else {
             zenModeInteractor.deactivateAllModes()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index c1e8032..40e6d28 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -30,6 +30,7 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.classifier.Classifier
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -41,6 +42,7 @@
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.wallpapers.ui.viewmodel.WallpaperViewModel
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -62,6 +64,8 @@
     private val splitEdgeDetector: SplitEdgeDetector,
     private val logger: SceneLogger,
     hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
+    val lightRevealScrim: LightRevealScrimViewModel,
+    val wallpaperViewModel: WallpaperViewModel,
     @Assisted view: View,
     @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
 ) : ExclusiveActivatable() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
index 2705cda..39703ab 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateTraceLogger.kt
@@ -25,6 +25,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
@@ -34,7 +36,7 @@
 @Inject
 constructor(
     private val shadeInteractor: ShadeInteractor,
-    private val shadeDisplaysRepository: ShadeDisplaysRepository,
+    private val shadeDisplaysRepository: Lazy<ShadeDisplaysRepository>,
     @Application private val scope: CoroutineScope,
 ) : CoreStartable {
     override fun start() {
@@ -52,9 +54,11 @@
                     instantForGroup(TRACK_GROUP_NAME, "shadeExpansion", it)
                 }
             }
-            launch {
-                shadeDisplaysRepository.displayId.collect {
-                    instantForGroup(TRACK_GROUP_NAME, "displayId", it)
+            if (ShadeWindowGoesAround.isEnabled) {
+                launch {
+                    shadeDisplaysRepository.get().displayId.collect {
+                        instantForGroup(TRACK_GROUP_NAME, "displayId", it)
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 2885ce8..a56624c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -84,7 +84,7 @@
                 scrim.width * initialWidthMultiplier + -scrim.width * ovalWidthIncreaseAmount,
                 scrim.height * OVAL_INITIAL_TOP_PERCENT - scrim.height * interpolatedAmount,
                 scrim.width * (1f - initialWidthMultiplier) + scrim.width * ovalWidthIncreaseAmount,
-                scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount
+                scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount,
             )
         }
     }
@@ -98,7 +98,7 @@
             /* controlX1= */ 0.4f,
             /* controlY1= */ 0f,
             /* controlX2= */ 0.2f,
-            /* controlY2= */ 1f
+            /* controlY2= */ 1f,
         )
 
     override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
@@ -109,14 +109,14 @@
         scrim.startColorAlpha =
             getPercentPastThreshold(
                 1 - interpolatedAmount,
-                threshold = 1 - START_COLOR_REVEAL_PERCENTAGE
+                threshold = 1 - START_COLOR_REVEAL_PERCENTAGE,
             )
 
         scrim.revealGradientEndColorAlpha =
             1f -
                 getPercentPastThreshold(
                     interpolatedAmount,
-                    threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+                    threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE,
                 )
 
         // Start changing gradient bounds later to avoid harsh gradient in the beginning
@@ -127,14 +127,14 @@
                 left = scrim.viewWidth / 2 - (scrim.viewWidth / 2) * gradientBoundsAmount,
                 top = 0f,
                 right = scrim.viewWidth / 2 + (scrim.viewWidth / 2) * gradientBoundsAmount,
-                bottom = scrim.viewHeight.toFloat()
+                bottom = scrim.viewHeight.toFloat(),
             )
         } else {
             scrim.setRevealGradientBounds(
                 left = 0f,
                 top = scrim.viewHeight / 2 - (scrim.viewHeight / 2) * gradientBoundsAmount,
                 right = scrim.viewWidth.toFloat(),
-                bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount
+                bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount,
             )
         }
     }
@@ -166,7 +166,7 @@
             1f -
                 getPercentPastThreshold(
                     amount,
-                    threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+                    threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE,
                 )
 
         val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1f, amount)
@@ -175,14 +175,14 @@
                 left = -(scrim.viewWidth) * gradientBoundsAmount,
                 top = -(scrim.viewHeight) * gradientBoundsAmount,
                 right = (scrim.viewWidth) * gradientBoundsAmount,
-                bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount
+                bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount,
             )
         } else {
             scrim.setRevealGradientBounds(
                 left = -(scrim.viewWidth) * gradientBoundsAmount,
                 top = -(scrim.viewHeight) * gradientBoundsAmount,
                 right = (scrim.viewWidth) + (scrim.viewWidth) * gradientBoundsAmount,
-                bottom = (scrim.viewHeight) * gradientBoundsAmount
+                bottom = (scrim.viewHeight) * gradientBoundsAmount,
             )
         }
     }
@@ -212,7 +212,7 @@
     /** Radius of initial state of circle reveal */
     val startRadius: Int,
     /** Radius of end state of circle reveal */
-    val endRadius: Int
+    val endRadius: Int,
 ) : LightRevealEffect {
     override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
         // reveal amount updates already have an interpolator, so we intentionally use the
@@ -225,7 +225,7 @@
             centerX - radius /* left */,
             centerY - radius /* top */,
             centerX + radius /* right */,
-            centerY + radius /* bottom */
+            centerY + radius, /* bottom */
         )
     }
 }
@@ -259,7 +259,7 @@
                     powerButtonY - height * interpolatedAmount,
                     width * (1f + OFF_SCREEN_START_AMOUNT) +
                         width * INCREASE_MULTIPLIER * interpolatedAmount,
-                    powerButtonY + height * interpolatedAmount
+                    powerButtonY + height * interpolatedAmount,
                 )
             } else if (rotation == RotationUtils.ROTATION_LANDSCAPE) {
                 setRevealGradientBounds(
@@ -268,7 +268,7 @@
                         height * INCREASE_MULTIPLIER * interpolatedAmount,
                     powerButtonY + width * interpolatedAmount,
                     (-height * OFF_SCREEN_START_AMOUNT) +
-                        height * INCREASE_MULTIPLIER * interpolatedAmount
+                        height * INCREASE_MULTIPLIER * interpolatedAmount,
                 )
             } else {
                 // RotationUtils.ROTATION_SEASCAPE
@@ -278,7 +278,7 @@
                         height * INCREASE_MULTIPLIER * interpolatedAmount,
                     (width - powerButtonY) + width * interpolatedAmount,
                     height * (1f + OFF_SCREEN_START_AMOUNT) +
-                        height * INCREASE_MULTIPLIER * interpolatedAmount
+                        height * INCREASE_MULTIPLIER * interpolatedAmount,
                 )
             }
         }
@@ -296,9 +296,9 @@
 @JvmOverloads
 constructor(
     context: Context?,
-    attrs: AttributeSet?,
+    attrs: AttributeSet? = null,
     initialWidth: Int? = null,
-    initialHeight: Int? = null
+    initialHeight: Int? = null,
 ) : View(context, attrs) {
 
     private val logString = this::class.simpleName!! + "@" + hashCode()
@@ -322,8 +322,9 @@
                 revealEffect.setRevealAmountOnScrim(value, this)
                 updateScrimOpaque()
                 TrackTracer.instantForGroup(
-                    "scrim", { "light_reveal_amount $logString" },
-                    (field * 100).toInt()
+                    "scrim",
+                    { "light_reveal_amount $logString" },
+                    (field * 100).toInt(),
                 )
                 invalidate()
             }
@@ -440,7 +441,7 @@
                     1f,
                     intArrayOf(Color.TRANSPARENT, Color.WHITE),
                     floatArrayOf(0f, 1f),
-                    Shader.TileMode.CLAMP
+                    Shader.TileMode.CLAMP,
                 )
 
             // SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
@@ -466,6 +467,7 @@
         viewWidth = measuredWidth
         viewHeight = measuredHeight
     }
+
     /**
      * Sets bounds for the transparent oval gradient that reveals the views below the scrim. This is
      * simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and
@@ -513,7 +515,7 @@
         gradientPaint.colorFilter =
             PorterDuffColorFilter(
                 getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
-                PorterDuff.Mode.MULTIPLY
+                PorterDuff.Mode.MULTIPLY,
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 4fb8f72..541a07c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.chips.call.ui.viewmodel
 
 import android.view.View
-import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.Cuj
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
@@ -37,6 +37,7 @@
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -92,7 +93,8 @@
                             OngoingActivityChipModel.Shown.IconOnly(
                                 icon = icon,
                                 colors = colors,
-                                getOnClickListener(state),
+                                onClickListenerLegacy = getOnClickListener(state),
+                                clickBehavior = getClickBehavior(state),
                             )
                         } else {
                             val startTimeInElapsedRealtime =
@@ -102,7 +104,8 @@
                                 icon = icon,
                                 colors = colors,
                                 startTimeMs = startTimeInElapsedRealtime,
-                                getOnClickListener(state),
+                                onClickListenerLegacy = getOnClickListener(state),
+                                clickBehavior = getClickBehavior(state),
                             )
                         }
                     }
@@ -116,6 +119,7 @@
         }
 
         return View.OnClickListener { view ->
+            StatusBarChipsModernization.assertInLegacyMode()
             logger.log(TAG, LogLevel.INFO, {}, { "Chip clicked" })
             val backgroundView =
                 view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background)
@@ -124,12 +128,33 @@
                 state.intent,
                 ActivityTransitionAnimator.Controller.fromView(
                     backgroundView,
-                    InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+                    Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
                 ),
             )
         }
     }
 
+    private fun getClickBehavior(
+        state: OngoingCallModel.InCall
+    ): OngoingActivityChipModel.ClickBehavior =
+        if (state.intent == null) {
+            OngoingActivityChipModel.ClickBehavior.None
+        } else {
+            OngoingActivityChipModel.ClickBehavior.ExpandAction(
+                onClick = { expandable ->
+                    StatusBarChipsModernization.assertInNewMode()
+                    val animationController =
+                        expandable.activityTransitionController(
+                            Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP
+                        )
+                    activityStarter.postStartActivityDismissingKeyguard(
+                        state.intent,
+                        animationController,
+                    )
+                }
+            )
+        }
+
     companion object {
         private val phoneIcon =
             Icon.Resource(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index 3422337..baa8eec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -204,13 +205,25 @@
             colors = ColorsModel.Red,
             // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
             startTimeMs = systemClock.elapsedRealtime(),
-            createDialogLaunchOnClickListener(
-                createCastScreenToOtherDeviceDialogDelegate(state),
-                dialogTransitionAnimator,
-                DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Cast to other device"),
-                logger,
-                TAG,
-            ),
+            onClickListenerLegacy =
+                createDialogLaunchOnClickListener(
+                    createCastScreenToOtherDeviceDialogDelegate(state),
+                    dialogTransitionAnimator,
+                    DIALOG_CUJ,
+                    logger,
+                    TAG,
+                ),
+            clickBehavior =
+                OngoingActivityChipModel.ClickBehavior.ExpandAction(
+                    onClick =
+                        createDialogLaunchOnClickCallback(
+                            createCastScreenToOtherDeviceDialogDelegate(state),
+                            dialogTransitionAnimator,
+                            DIALOG_CUJ,
+                            logger,
+                            TAG,
+                        )
+                ),
         )
     }
 
@@ -225,16 +238,24 @@
                     )
                 ),
             colors = ColorsModel.Red,
-            createDialogLaunchOnClickListener(
-                createGenericCastToOtherDeviceDialogDelegate(deviceName),
-                dialogTransitionAnimator,
-                DialogCuj(
-                    Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
-                    tag = "Cast to other device audio only",
+            onClickListenerLegacy =
+                createDialogLaunchOnClickListener(
+                    createGenericCastToOtherDeviceDialogDelegate(deviceName),
+                    dialogTransitionAnimator,
+                    DIALOG_CUJ_AUDIO_ONLY,
+                    logger,
+                    TAG,
                 ),
-                logger,
-                TAG,
-            ),
+            clickBehavior =
+                OngoingActivityChipModel.ClickBehavior.ExpandAction(
+                    createDialogLaunchOnClickCallback(
+                        createGenericCastToOtherDeviceDialogDelegate(deviceName),
+                        dialogTransitionAnimator,
+                        DIALOG_CUJ_AUDIO_ONLY,
+                        logger,
+                        TAG,
+                    )
+                ),
         )
     }
 
@@ -256,6 +277,13 @@
 
     companion object {
         @DrawableRes val CAST_TO_OTHER_DEVICE_ICON = R.drawable.ic_cast_connected
+        private val DIALOG_CUJ =
+            DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Cast to other device")
+        private val DIALOG_CUJ_AUDIO_ONLY =
+            DialogCuj(
+                Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+                tag = "Cast to other device audio only",
+            )
         private val TAG = "CastToOtherVM".pad()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 18b0dee..b7cad62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -83,6 +83,7 @@
                     )
                 }
             }
+        val clickBehavior = OngoingActivityChipModel.ClickBehavior.None
 
         val isShowingHeadsUpFromChipTap =
             headsUpState is TopPinnedState.Pinned &&
@@ -91,7 +92,12 @@
         if (isShowingHeadsUpFromChipTap) {
             // If the user tapped this chip to show the HUN, we want to just show the icon because
             // the HUN will show the rest of the information.
-            return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+            return OngoingActivityChipModel.Shown.IconOnly(
+                icon,
+                colors,
+                onClickListener,
+                clickBehavior,
+            )
         }
 
         if (this.promotedContent.shortCriticalText != null) {
@@ -100,6 +106,7 @@
                 colors,
                 this.promotedContent.shortCriticalText,
                 onClickListener,
+                clickBehavior,
             )
         }
 
@@ -111,11 +118,21 @@
             // notification will likely just be set to the current time, which would cause the chip
             // to always show "now". We don't want early testers to get that experience since it's
             // not what will happen at launch, so just don't show any time.
-            return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+            return OngoingActivityChipModel.Shown.IconOnly(
+                icon,
+                colors,
+                onClickListener,
+                clickBehavior,
+            )
         }
 
         if (this.promotedContent.time == null) {
-            return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+            return OngoingActivityChipModel.Shown.IconOnly(
+                icon,
+                colors,
+                onClickListener,
+                clickBehavior,
+            )
         }
         when (this.promotedContent.time.mode) {
             PromotedNotificationContentModel.When.Mode.BasicTime -> {
@@ -124,6 +141,7 @@
                     colors,
                     time = this.promotedContent.time.time,
                     onClickListener,
+                    clickBehavior,
                 )
             }
             PromotedNotificationContentModel.When.Mode.CountUp -> {
@@ -132,6 +150,7 @@
                     colors,
                     startTimeMs = this.promotedContent.time.time,
                     onClickListener,
+                    clickBehavior,
                 )
             }
             PromotedNotificationContentModel.When.Mode.CountDown -> {
@@ -141,6 +160,7 @@
                     colors,
                     startTimeMs = this.promotedContent.time.time,
                     onClickListener,
+                    clickBehavior,
                 )
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 0065593..7f2327a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.time.SystemClock
@@ -91,16 +92,24 @@
                                 ),
                             colors = ColorsModel.Red,
                             startTimeMs = systemClock.elapsedRealtime(),
-                            createDialogLaunchOnClickListener(
-                                createDelegate(state.recordedTask),
-                                dialogTransitionAnimator,
-                                DialogCuj(
-                                    Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
-                                    tag = "Screen record",
+                            onClickListenerLegacy =
+                                createDialogLaunchOnClickListener(
+                                    createDelegate(state.recordedTask),
+                                    dialogTransitionAnimator,
+                                    DIALOG_CUJ,
+                                    logger,
+                                    TAG,
                                 ),
-                                logger,
-                                TAG,
-                            ),
+                            clickBehavior =
+                                OngoingActivityChipModel.ClickBehavior.ExpandAction(
+                                    createDialogLaunchOnClickCallback(
+                                        dialogDelegate = createDelegate(state.recordedTask),
+                                        dialogTransitionAnimator = dialogTransitionAnimator,
+                                        DIALOG_CUJ,
+                                        logger,
+                                        TAG,
+                                    )
+                                ),
                         )
                     }
                 }
@@ -154,6 +163,8 @@
 
     companion object {
         @DrawableRes val ICON = R.drawable.ic_screenrecord
+        private val DIALOG_CUJ =
+            DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Screen record")
         private val TAG = "ScreenRecordVM".pad()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 2af86a5..6654d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -128,13 +129,25 @@
             colors = ColorsModel.Red,
             // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
             startTimeMs = systemClock.elapsedRealtime(),
-            createDialogLaunchOnClickListener(
-                createShareScreenToAppDialogDelegate(state),
-                dialogTransitionAnimator,
-                DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app"),
-                logger,
-                TAG,
-            ),
+            onClickListenerLegacy =
+                createDialogLaunchOnClickListener(
+                    createShareScreenToAppDialogDelegate(state),
+                    dialogTransitionAnimator,
+                    DIALOG_CUJ,
+                    logger,
+                    TAG,
+                ),
+            clickBehavior =
+                OngoingActivityChipModel.ClickBehavior.ExpandAction(
+                    onClick =
+                        createDialogLaunchOnClickCallback(
+                            createShareScreenToAppDialogDelegate(state),
+                            dialogTransitionAnimator,
+                            DIALOG_CUJ,
+                            logger,
+                            TAG,
+                        )
+                ),
         )
     }
 
@@ -150,16 +163,24 @@
                     )
                 ),
             colors = ColorsModel.Red,
-            createDialogLaunchOnClickListener(
-                createGenericShareToAppDialogDelegate(),
-                dialogTransitionAnimator,
-                DialogCuj(
-                    Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
-                    tag = "Share to app audio only",
+            onClickListenerLegacy =
+                createDialogLaunchOnClickListener(
+                    createGenericShareToAppDialogDelegate(),
+                    dialogTransitionAnimator,
+                    DIALOG_CUJ_AUDIO_ONLY,
+                    logger,
+                    TAG,
                 ),
-                logger,
-                TAG,
-            ),
+            clickBehavior =
+                OngoingActivityChipModel.ClickBehavior.ExpandAction(
+                    createDialogLaunchOnClickCallback(
+                        createGenericShareToAppDialogDelegate(),
+                        dialogTransitionAnimator,
+                        DIALOG_CUJ_AUDIO_ONLY,
+                        logger,
+                        TAG,
+                    )
+                ),
         )
     }
 
@@ -180,6 +201,10 @@
 
     companion object {
         @DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all
+        private val DIALOG_CUJ =
+            DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app")
+        private val DIALOG_CUJ_AUDIO_ONLY =
+            DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app audio only")
         private val TAG = "ShareToAppVM".pad()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index b0fa9d8..d46638f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -56,7 +56,8 @@
                 // Data
                 setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView, iconViewStore)
                 setChipMainContent(chipModel, chipTextView, chipTimeView, chipShortTimeDeltaView)
-                viewBinding.rootView.setOnClickListener(chipModel.onClickListener)
+
+                viewBinding.rootView.setOnClickListener(chipModel.onClickListenerLegacy)
                 updateChipPadding(
                     chipModel,
                     chipBackgroundView,
@@ -424,7 +425,7 @@
         // Clickable chips need to be a minimum size for accessibility purposes, but let
         // non-clickable chips be smaller.
         val minimumWidth =
-            if (chipModel.onClickListener != null) {
+            if (chipModel.onClickListenerLegacy != null) {
                 chipBackgroundView.context.resources.getDimensionPixelSize(
                     R.dimen.min_clickable_item_size
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index 1be5842..6ce3228 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -35,10 +35,13 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.animation.Expandable
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.ui.compose.modifiers.neverDecreaseWidth
@@ -47,23 +50,42 @@
 
 @Composable
 fun OngoingActivityChip(model: OngoingActivityChipModel.Shown, modifier: Modifier = Modifier) {
+    when (val clickBehavior = model.clickBehavior) {
+        is OngoingActivityChipModel.ClickBehavior.ExpandAction -> {
+            // Wrap the chip in an Expandable so we can animate the expand transition.
+            ExpandableChip(
+                color = { Color.Transparent },
+                shape =
+                    RoundedCornerShape(
+                        dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)
+                    ),
+                modifier = modifier,
+            ) { expandable ->
+                ChipBody(model, onClick = { clickBehavior.onClick(expandable) })
+            }
+        }
+
+        is OngoingActivityChipModel.ClickBehavior.None -> {
+            ChipBody(model, modifier = modifier)
+        }
+    }
+}
+
+@Composable
+private fun ChipBody(
+    model: OngoingActivityChipModel.Shown,
+    modifier: Modifier = Modifier,
+    onClick: () -> Unit = {},
+) {
     val context = LocalContext.current
-    val isClickable = model.onClickListener != null
+    val isClickable = onClick != {}
     val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView
 
     // Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
     // height of the chip is determined by the height of the background of the Row below.
     Box(
         contentAlignment = Alignment.Center,
-        modifier =
-            modifier
-                .fillMaxHeight()
-                .clickable(
-                    enabled = isClickable,
-                    onClick = {
-                        // TODO(b/372657935): Implement click actions.
-                    },
-                ),
+        modifier = modifier.fillMaxHeight().clickable(enabled = isClickable, onClick = onClick),
     ) {
         Row(
             horizontalArrangement = Arrangement.Center,
@@ -206,3 +228,13 @@
         }
     }
 }
+
+@Composable
+private fun ExpandableChip(
+    color: () -> Color,
+    shape: Shape,
+    modifier: Modifier = Modifier,
+    content: @Composable (Expandable) -> Unit,
+) {
+    Expandable(color = color(), shape = shape, modifier = modifier.clip(shape)) { content(it) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 956d99e..68c8f8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.chips.ui.model
 
 import android.view.View
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -46,17 +47,20 @@
         open val colors: ColorsModel,
         /**
          * Listener method to invoke when this chip is clicked. If null, the chip won't be
-         * clickable.
+         * clickable. Will be deprecated after [StatusBarChipsModernization] is enabled.
          */
-        open val onClickListener: View.OnClickListener?,
+        open val onClickListenerLegacy: View.OnClickListener?,
+        /** Data class that determines how clicks on the chip should be handled. */
+        open val clickBehavior: ClickBehavior,
     ) : OngoingActivityChipModel() {
 
         /** This chip shows only an icon and nothing else. */
         data class IconOnly(
             override val icon: ChipIcon,
             override val colors: ColorsModel,
-            override val onClickListener: View.OnClickListener?,
-        ) : Shown(icon, colors, onClickListener) {
+            override val onClickListenerLegacy: View.OnClickListener?,
+            override val clickBehavior: ClickBehavior,
+        ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) {
             override val logName = "Shown.Icon"
         }
 
@@ -74,8 +78,9 @@
              * [android.widget.Chronometer.setBase].
              */
             val startTimeMs: Long,
-            override val onClickListener: View.OnClickListener?,
-        ) : Shown(icon, colors, onClickListener) {
+            override val onClickListenerLegacy: View.OnClickListener?,
+            override val clickBehavior: ClickBehavior,
+        ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) {
             override val logName = "Shown.Timer"
         }
 
@@ -88,8 +93,9 @@
             override val colors: ColorsModel,
             /** The time of the event that this chip represents. */
             val time: Long,
-            override val onClickListener: View.OnClickListener?,
-        ) : Shown(icon, colors, onClickListener) {
+            override val onClickListenerLegacy: View.OnClickListener?,
+            override val clickBehavior: ClickBehavior,
+        ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) {
             init {
                 StatusBarNotifChips.assertInNewMode()
             }
@@ -105,7 +111,13 @@
             override val colors: ColorsModel,
             /** The number of seconds until an event is started. */
             val secondsUntilStarted: Long,
-        ) : Shown(icon = null, colors, onClickListener = null) {
+        ) :
+            Shown(
+                icon = null,
+                colors,
+                onClickListenerLegacy = null,
+                clickBehavior = ClickBehavior.None,
+            ) {
             override val logName = "Shown.Countdown"
         }
 
@@ -115,8 +127,9 @@
             override val colors: ColorsModel,
             // TODO(b/361346412): Enforce a max length requirement?
             val text: String,
-            override val onClickListener: View.OnClickListener? = null,
-        ) : Shown(icon, colors, onClickListener) {
+            override val onClickListenerLegacy: View.OnClickListener? = null,
+            override val clickBehavior: ClickBehavior,
+        ) : Shown(icon, colors, onClickListenerLegacy, clickBehavior) {
             override val logName = "Shown.Text"
         }
     }
@@ -149,4 +162,13 @@
          */
         data class SingleColorIcon(val impl: Icon) : ChipIcon
     }
+
+    /** Defines the behavior of the chip when it is clicked. */
+    sealed interface ClickBehavior {
+        /** No specific click behavior. */
+        data object None : ClickBehavior
+
+        /** The chip expands into a dialog or activity on click. */
+        data class ExpandAction(val onClick: (Expandable) -> Unit) : ClickBehavior
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
index 2fc366b..a978c04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
@@ -19,6 +19,7 @@
 import android.view.View
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.res.R
@@ -26,6 +27,7 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import kotlinx.coroutines.flow.StateFlow
 
 /**
@@ -46,6 +48,7 @@
             tag: String,
         ): View.OnClickListener {
             return View.OnClickListener { view ->
+                StatusBarChipsModernization.assertInLegacyMode()
                 logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" })
                 val dialog = dialogDelegate.createDialog()
                 val launchableView =
@@ -55,5 +58,28 @@
                 dialogTransitionAnimator.showFromView(dialog, launchableView, cuj)
             }
         }
+
+        /**
+         * Creates a chip click callback with an [Expandable] parameter that launches a dialog
+         * created by [dialogDelegate].
+         */
+        fun createDialogLaunchOnClickCallback(
+            dialogDelegate: SystemUIDialog.Delegate,
+            dialogTransitionAnimator: DialogTransitionAnimator,
+            cuj: DialogCuj,
+            @StatusBarChipsLog logger: LogBuffer,
+            tag: String,
+        ): (Expandable) -> Unit {
+            return { expandable ->
+                StatusBarChipsModernization.assertInNewMode()
+                logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" })
+                val dialog = dialogDelegate.createDialog()
+
+                val controller = expandable.dialogTransitionController(cuj)
+                if (controller != null) {
+                    dialogTransitionAnimator.show(dialog, controller)
+                }
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index cf9ee61..826329d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -39,7 +39,6 @@
 import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
 import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
 
-import static com.android.systemui.Flags.notificationsDismissPrunedSummaries;
 import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED;
 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED;
@@ -278,9 +277,7 @@
         Assert.isMainThread();
         checkForReentrantCall();
 
-        if (notificationsDismissPrunedSummaries()) {
-            entriesToDismiss = includeSummariesToDismiss(entriesToDismiss);
-        }
+        entriesToDismiss = includeSummariesToDismiss(entriesToDismiss);
 
         final int entryCount = entriesToDismiss.size();
         final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index c88dd7a..d401283 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -154,10 +154,12 @@
         DumpUtilsKt.withIncreasedIndent(pw, () -> {
             // TODO: b/375010573 - update dumps for redesign
             pw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility()));
-            pw.println("manageButton visibility: "
-                    + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility()));
-            pw.println("dismissButton visibility: "
-                    + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility()));
+            if (mManageOrHistoryButton != null)
+                pw.println("mManageOrHistoryButton visibility: "
+                        + DumpUtilsKt.visibilityString(mManageOrHistoryButton.getVisibility()));
+            if (mClearAllButton != null)
+                pw.println("mClearAllButton visibility: "
+                        + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility()));
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt
index d3359d3..6bcce3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.logging.dagger
 
+import com.android.app.tracing.TrackGroupUtils.trackGroup
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
@@ -44,7 +45,11 @@
     @SysUISingleton
     @NotificationHeadsUpLog
     fun provideNotificationHeadsUpLogBuffer(factory: LogBufferFactory): LogBuffer {
-        return factory.create("NotifHeadsUpLog", 1000)
+        return factory.create(
+            "NotifHeadsUpLog",
+            1000,
+            systraceTrackName = notifPipelineTrack("NotifHeadsUpLog"),
+        )
     }
 
     /** Provides a logging buffer for logs related to inflation of notifications. */
@@ -52,7 +57,11 @@
     @SysUISingleton
     @NotifInflationLog
     fun provideNotifInflationLogBuffer(factory: LogBufferFactory): LogBuffer {
-        return factory.create("NotifInflationLog", 250)
+        return factory.create(
+            "NotifInflationLog",
+            250,
+            systraceTrackName = notifPipelineTrack("NotifInflationLog"),
+        )
     }
 
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
@@ -60,7 +69,11 @@
     @SysUISingleton
     @NotifInteractionLog
     fun provideNotifInteractionLogBuffer(factory: LogBufferFactory): LogBuffer {
-        return factory.create("NotifInteractionLog", 50)
+        return factory.create(
+            "NotifInteractionLog",
+            50,
+            systraceTrackName = notifPipelineTrack("NotifInteractionLog"),
+        )
     }
 
     /** Provides a logging buffer for notification interruption calculations. */
@@ -68,7 +81,11 @@
     @SysUISingleton
     @NotificationInterruptLog
     fun provideNotificationInterruptLogBuffer(factory: LogBufferFactory): LogBuffer {
-        return factory.create("NotifInterruptLog", 100)
+        return factory.create(
+            "NotifInterruptLog",
+            100,
+            systraceTrackName = notifPipelineTrack("NotifInterruptLog"),
+        )
     }
 
     /** Provides a logging buffer for all logs related to notifications on the lockscreen. */
@@ -91,7 +108,12 @@
         if (Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()) {
             maxSize *= 10
         }
-        return factory.create("NotifLog", maxSize, Compile.IS_DEBUG /* systrace */)
+        return factory.create(
+            "NotifLog",
+            maxSize,
+            /* systrace= */ Compile.IS_DEBUG,
+            systraceTrackName = notifPipelineTrack("NotifLog"),
+        )
     }
 
     /** Provides a logging buffer for all logs related to remote input controller. */
@@ -107,7 +129,11 @@
     @SysUISingleton
     @NotificationRenderLog
     fun provideNotificationRenderLogBuffer(factory: LogBufferFactory): LogBuffer {
-        return factory.create("NotifRenderLog", 100)
+        return factory.create(
+            "NotifRenderLog",
+            100,
+            systraceTrackName = notifPipelineTrack("NotifRenderLog"),
+        )
     }
 
     /** Provides a logging buffer for all logs related to managing notification sections. */
@@ -150,3 +176,13 @@
         return factory.create("VisualStabilityLog", 50, /* maxSize */ false /* systrace */)
     }
 }
+
+private const val NOTIF_PIPELINE_TRACK_GROUP_NAME = "Notification pipeline"
+
+/**
+ * This generates a track name that is hierarcically collapsed inside
+ * [NOTIF_PIPELINE_TRACK_GROUP_NAME] in perfetto traces.
+ */
+private fun notifPipelineTrack(trackName: String): String {
+    return trackGroup(NOTIF_PIPELINE_TRACK_GROUP_NAME, trackName)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
new file mode 100644
index 0000000..fa1f32c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.promoted
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the promoted ongoing notifications AOD flag state. */
+object PromotedNotificationUiAod {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_AOD_UI_RICH_ONGOING
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.aodUiRichOngoing()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is not enabled to ensure that the refactor author catches issues in testing.
+     * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+     */
+    @JvmStatic
+    inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
new file mode 100644
index 0000000..cb0d674
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the expanded ui rich ongoing flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object PromotedNotificationUiForceExpanded {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING_FORCE_EXPANDED
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.uiRichOngoingForceExpanded()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This will throw an exception if
+     * the flag is not enabled to ensure that the refactor author catches issues in testing.
+     * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+     */
+    @JvmStatic
+    inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
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 495b508..f571071 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
@@ -127,7 +127,6 @@
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState;
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.util.Assert;
@@ -282,11 +281,9 @@
     private boolean mExpandedInThisMotion;
     private boolean mShouldShowShelfOnly;
     protected boolean mScrollingEnabled;
-    private boolean mIsCurrentUserSetup;
     protected FooterView mFooterView;
     protected EmptyShadeView mEmptyShadeView;
     private boolean mClearAllInProgress;
-    private FooterClearAllListener mFooterClearAllListener;
     private boolean mFlingAfterUpEvent;
     /**
      * Was the scroller scrolled to the top when the down motion was observed?
@@ -467,7 +464,6 @@
     boolean mHeadsUpAnimatingAway;
     private Consumer<Boolean> mHeadsUpAnimatingAwayListener;
     private int mStatusBarState;
-    private int mUpcomingStatusBarState;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
     private final Runnable mReflingAndAnimateScroll = this::animateScroll;
     private int mCornerRadius;
@@ -498,7 +494,6 @@
     private float mLastSentExpandedHeight;
     private boolean mWillExpand;
     private int mGapHeight;
-    private boolean mIsRemoteInputActive;
 
     /**
      * The extra inset during the full shade transition
@@ -572,10 +567,8 @@
     private boolean mDismissUsingRowTranslationX = true;
     private ExpandableNotificationRow mTopHeadsUpRow;
     private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
-    private final ScreenOffAnimationController mScreenOffAnimationController;
     private boolean mShouldUseSplitNotificationShade;
     private boolean mShouldSkipTopPaddingAnimationAfterFold = false;
-    private boolean mHasFilteredOutSeenNotifications;
     @Nullable private SplitShadeStateController mSplitShadeStateController = null;
     private boolean mIsSmallLandscapeLockscreenEnabled = false;
     private boolean mSuppressHeightUpdates;
@@ -636,9 +629,6 @@
     };
 
     @Nullable
-    private OnClickListener mManageButtonClickListener;
-
-    @Nullable
     private WallpaperInteractor mWallpaperInteractor;
 
     public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
@@ -650,8 +640,6 @@
         mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
         mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
-        mScreenOffAnimationController =
-                Dependency.get(ScreenOffAnimationController.class);
         mSectionsManager.initialize(this);
         mSections = mSectionsManager.createSectionsForBuckets();
 
@@ -5403,7 +5391,6 @@
             println(pw, "suppressChildrenMeasureLayout", mSuppressChildrenMeasureAndLayout);
             println(pw, "scrollY", mAmbientState.getScrollY());
             println(pw, "showShelfOnly", mShouldShowShelfOnly);
-            println(pw, "isCurrentUserSetup", mIsCurrentUserSetup);
             println(pw, "hideAmount", mAmbientState.getHideAmount());
             println(pw, "ambientStateSwipingUp", mAmbientState.isSwipingUp());
             println(pw, "maxDisplayedNotifications", mMaxDisplayedNotifications);
@@ -6793,10 +6780,6 @@
         void onClearAll(@SelectedRows int selectedRows);
     }
 
-    interface FooterClearAllListener {
-        void onClearAll();
-    }
-
     interface ClearAllAnimationListener {
         void onAnimationEnd(
                 List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows);
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 f0455fc..c1d0226 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
@@ -49,9 +49,11 @@
 import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
@@ -131,9 +133,12 @@
     private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
     private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
     private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
+    private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel,
     dozingToGlanceableHubTransitionViewModel: DozingToGlanceableHubTransitionViewModel,
     private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
     private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
+    private val dozingToPrimaryBouncerTransitionViewModel:
+        DozingToPrimaryBouncerTransitionViewModel,
     private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
     private val glanceableHubToLockscreenTransitionViewModel:
         GlanceableHubToLockscreenTransitionViewModel,
@@ -554,8 +559,10 @@
             aodToGoneTransitionViewModel.notificationAlpha(viewState),
             aodToLockscreenTransitionViewModel.notificationAlpha,
             aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+            aodToPrimaryBouncerTransitionViewModel.notificationAlpha,
             dozingToLockscreenTransitionViewModel.lockscreenAlpha,
             dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+            dozingToPrimaryBouncerTransitionViewModel.notificationAlpha,
             dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
             goneToAodTransitionViewModel.notificationAlpha,
             goneToDreamingTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 4751293..5a63c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -312,6 +312,25 @@
         }
     }
 
+    override fun postStartActivityDismissingKeyguard(
+        intent: Intent,
+        delay: Int,
+        animationController: ActivityTransitionAnimator.Controller?,
+        customMessage: String?,
+        userHandle: UserHandle?,
+    ) {
+        postOnUiThread(delay) {
+            activityStarterInternal.startActivityDismissingKeyguard(
+                intent = intent,
+                onlyProvisioned = true,
+                dismissShade = true,
+                animationController = animationController,
+                customMessage = customMessage,
+                userHandle = userHandle,
+            )
+        }
+    }
+
     override fun dismissKeyguardThenExecute(
         action: OnDismissAction,
         cancel: Runnable?,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index d43fed0..2cd8eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -31,7 +31,6 @@
 import android.annotation.IntDef;
 import android.graphics.Color;
 import android.os.Handler;
-import android.os.Trace;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Pair;
@@ -53,6 +52,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dagger.SysUISingleton;
@@ -82,6 +82,9 @@
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -90,9 +93,6 @@
 
 import javax.inject.Inject;
 
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
-
 /**
  * Controls both the scrim behind the notifications and in front of the notifications (when a
  * security method gets shown).
@@ -226,6 +226,8 @@
     private float mAdditionalScrimBehindAlphaKeyguard = 0f;
     // Combined scrim behind keyguard alpha of default scrim + additional scrim
     private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
+
+    static final float TRANSPARENT_BOUNCER_SCRIM_ALPHA = 0.54f;
     private final float mDefaultScrimAlpha;
 
     private float mRawPanelExpansionFraction;
@@ -340,7 +342,10 @@
             LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
         mScrimStateListener = lightBarController::setScrimState;
         mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
-        mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
+        // All scrims default alpha need to match bouncer background alpha to make sure the
+        // transitions involving the bouncer are smooth and don't overshoot the bouncer alpha.
+        mDefaultScrimAlpha =
+                Flags.bouncerUiRevamp() ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : BUSY_SCRIM_ALPHA;
 
         mKeyguardStateController = keyguardStateController;
         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
@@ -974,8 +979,6 @@
                         mBehindTint,
                         interpolatedFraction);
             }
-        } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
-            mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f);
         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
                 || mState == ScrimState.PULSING || mState == ScrimState.GLANCEABLE_HUB) {
             Pair<Integer, Float> result = calculateBackStateForState(mState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 8dcb663..1493729 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -16,17 +16,24 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.phone.ScrimController.BUSY_SCRIM_ALPHA;
+import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+
 import android.graphics.Color;
 
 import com.android.app.tracing.coroutines.TrackTracer;
+import com.android.systemui.Flags;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.res.R;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
 /**
  * Possible states of the ScrimController state machine.
  */
+@ExperimentalCoroutinesApi
 public enum ScrimState {
 
     /**
@@ -92,40 +99,19 @@
         }
     },
 
-    AUTH_SCRIMMED_SHADE {
-        @Override
-        public void prepare(ScrimState previousState) {
-            // notif scrim alpha values are determined by ScrimController#applyState
-            // based on the shade expansion
-
-            mFrontTint = mBackgroundColor;
-            mFrontAlpha = .66f;
-
-            mBehindTint = mBackgroundColor;
-            mBehindAlpha = 1f;
-        }
-    },
-
-    AUTH_SCRIMMED {
-        @Override
-        public void prepare(ScrimState previousState) {
-            mNotifTint = previousState.mNotifTint;
-            mNotifAlpha = previousState.mNotifAlpha;
-
-            mBehindTint = previousState.mBehindTint;
-            mBehindAlpha = previousState.mBehindAlpha;
-
-            mFrontTint = mBackgroundColor;
-            mFrontAlpha = .66f;
-        }
-    },
-
     /**
      * Showing password challenge on the keyguard.
      */
     BOUNCER {
         @Override
         public void prepare(ScrimState previousState) {
+            if (Flags.bouncerUiRevamp()) {
+                mBehindAlpha = mClipQsScrim ? 0.0f : TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+                mNotifAlpha = mClipQsScrim ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : 0;
+                mBehindTint = mNotifTint = mSurfaceColor;
+                mFrontAlpha = 0f;
+                return;
+            }
             mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
             mBehindTint = mClipQsScrim ? mBackgroundColor : mSurfaceColor;
             mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0;
@@ -136,6 +122,10 @@
         @Override
         public void setSurfaceColor(int surfaceColor) {
             super.setSurfaceColor(surfaceColor);
+            if (Flags.bouncerUiRevamp()) {
+                mBehindTint = mNotifTint = mSurfaceColor;
+                return;
+            }
             if (!mClipQsScrim) {
                 mBehindTint = mSurfaceColor;
             }
@@ -146,15 +136,38 @@
      * Showing password challenge on top of a FLAG_SHOW_WHEN_LOCKED activity.
      */
     BOUNCER_SCRIMMED {
+        @ExperimentalCoroutinesApi
         @Override
         public void prepare(ScrimState previousState) {
+            if (Flags.bouncerUiRevamp()) {
+                mBehindAlpha = 0f;
+                mFrontAlpha = TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+                mFrontTint = mSurfaceColor;
+                return;
+            }
             mBehindAlpha = 0;
             mFrontAlpha = mDefaultScrimAlpha;
         }
+
+        @Override
+        public boolean shouldBlendWithMainColor() {
+            return !Flags.bouncerUiRevamp();
+        }
     },
 
     SHADE_LOCKED {
         @Override
+        public void setDefaultScrimAlpha(float defaultScrimAlpha) {
+            super.setDefaultScrimAlpha(defaultScrimAlpha);
+            if (!Flags.notificationShadeBlur()) {
+                // Temporary change that prevents the shade from being semi-transparent when
+                // bouncer blur is enabled but notification shade blur is not enabled. This is
+                // required to perf test these two flags independently.
+                mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
+            }
+        }
+
+        @Override
         public void prepare(ScrimState previousState) {
             mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
             mNotifAlpha = 1f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
index 2ab2b68..9f8b455 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
@@ -1,18 +1,18 @@
 /*
-* Copyright (C) 2024 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package com.android.systemui.statusbar.phone.ongoingcall
 
 import com.android.systemui.Flags
@@ -44,9 +44,16 @@
         RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
 
     /**
+     * Called to ensure code is only run when the flag is enabled. This will throw an exception if
+     * the flag is not enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+    /**
      * Called to ensure code is only run when the flag is disabled. This will throw an exception if
      * the flag is enabled to ensure that the refactor author catches issues in testing.
      */
     @JvmStatic
     inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 8daa803..7e76d77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -41,8 +41,8 @@
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
 import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index b1cc208..9c1171f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -57,6 +57,7 @@
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarIconBlockListBinder
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.HomeStatusBarViewModelFactory
 import javax.inject.Inject
@@ -304,11 +305,7 @@
 fun Disambiguation(viewModel: HomeStatusBarViewModel) {
     val clockVisibilityModel =
         viewModel.isClockVisible.collectAsStateWithLifecycle(
-            initialValue =
-                HomeStatusBarViewModel.VisibilityModel(
-                    visibility = View.GONE,
-                    shouldAnimateChange = false,
-                )
+            initialValue = VisibilityModel(visibility = View.GONE, shouldAnimateChange = false)
         )
     if (clockVisibilityModel.value.visibility == View.VISIBLE) {
         Box(modifier = Modifier.fillMaxSize().alpha(0.5f), contentAlignment = Alignment.Center) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt
new file mode 100644
index 0000000..e272252
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/SystemInfoCombinedVisibilityModel.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.pipeline.shared.ui.model
+
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
+
+/** The combined visibility + animation state for the system info status bar area */
+data class SystemInfoCombinedVisibilityModel(
+    val baseVisibility: VisibilityModel,
+    val animationState: SystemEventAnimationState,
+) : Diffable<SystemInfoCombinedVisibilityModel> {
+    override fun logDiffs(prevVal: SystemInfoCombinedVisibilityModel, row: TableRowLogger) {
+        if (animationState != prevVal.animationState) {
+            row.logChange(COL_ANIM, animationState.name)
+        }
+
+        baseVisibility.logDiffs(prevVal.baseVisibility, row)
+    }
+
+    override fun logFull(row: TableRowLogger) {
+        row.logChange(COL_ANIM, animationState.name)
+        baseVisibility.logFull(row)
+    }
+
+    companion object {
+        const val COL_ANIM = "animState"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt
new file mode 100644
index 0000000..7b39ada
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/VisibilityModel.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.pipeline.shared.ui.model
+
+import android.view.View
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.util.visibilityString
+
+/** Models the current visibility for a specific child view of status bar. */
+data class VisibilityModel(
+    @View.Visibility val visibility: Int,
+    /** True if a visibility change should be animated. */
+    val shouldAnimateChange: Boolean,
+) : Diffable<VisibilityModel> {
+    override fun logDiffs(prevVal: VisibilityModel, row: TableRowLogger) {
+        if (visibility != prevVal.visibility) {
+            row.logChange(COL_VIS, visibilityString(visibility))
+        }
+
+        if (shouldAnimateChange != prevVal.shouldAnimateChange) {
+            row.logChange(COL_ANIMATE, shouldAnimateChange)
+        }
+    }
+
+    override fun logFull(row: TableRowLogger) {
+        row.logChange(COL_VIS, visibilityString(visibility))
+        row.logChange(COL_ANIMATE, shouldAnimateChange)
+    }
+
+    companion object {
+        const val COL_VIS = "vis"
+        const val COL_ANIMATE = "animate"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index dcd2dbf..5acedf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -28,6 +28,8 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -39,7 +41,6 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
 import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
-import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
 import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
 import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipsViewModel
@@ -53,7 +54,8 @@
 import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarIconBlockListInteractor
 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarInteractor
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -155,19 +157,6 @@
      */
     val areaTint: Flow<StatusBarTintColor>
 
-    /** Models the current visibility for a specific child view of status bar. */
-    data class VisibilityModel(
-        @View.Visibility val visibility: Int,
-        /** True if a visibility change should be animated. */
-        val shouldAnimateChange: Boolean,
-    )
-
-    /** The combined visibility + animation state for the system info status bar area */
-    data class SystemInfoCombinedVisibilityModel(
-        val baseVisibility: VisibilityModel,
-        val animationState: SystemEventAnimationState,
-    )
-
     /** Interface for the assisted factory, to allow for providing a fake in tests */
     interface HomeStatusBarViewModelFactory {
         fun create(displayId: Int): HomeStatusBarViewModel
@@ -178,6 +167,7 @@
 @AssistedInject
 constructor(
     @Assisted thisDisplayId: Int,
+    tableLoggerFactory: TableLogBufferFactory,
     homeStatusBarInteractor: HomeStatusBarInteractor,
     homeStatusBarIconBlockListInteractor: HomeStatusBarIconBlockListInteractor,
     lightsOutInteractor: LightsOutInteractor,
@@ -196,9 +186,19 @@
     statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
     @Application coroutineScope: CoroutineScope,
 ) : HomeStatusBarViewModel {
+
+    val tableLogger = tableLoggerFactory.getOrCreate(tableLogBufferName(thisDisplayId), 200)
+
     override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
         keyguardTransitionInteractor
             .isInTransition(Edge.create(from = LOCKSCREEN, to = OCCLUDED))
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogBuffer = tableLogger,
+                columnPrefix = "",
+                columnName = COL_LOCK_TO_OCCLUDED,
+                initialValue = false,
+            )
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false)
 
     override val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> =
@@ -225,20 +225,33 @@
                 // which lives elsewhere.)
                 currentScene == Scenes.Gone || isOccluded
             }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogBuffer = tableLogger,
+                columnPrefix = "",
+                columnName = COL_ALLOWED_BY_SCENE,
+                initialValue = false,
+            )
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false)
 
     override val areNotificationsLightsOut: Flow<Boolean> =
         if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
-            emptyFlow()
-        } else {
-            combine(
-                    notificationsInteractor.areAnyNotificationsPresent,
-                    lightsOutInteractor.isLowProfile(thisDisplayId) ?: flowOf(false),
-                ) { hasNotifications, isLowProfile ->
-                    hasNotifications && isLowProfile
-                }
-                .distinctUntilChanged()
-        }
+                emptyFlow()
+            } else {
+                combine(
+                        notificationsInteractor.areAnyNotificationsPresent,
+                        lightsOutInteractor.isLowProfile(thisDisplayId) ?: flowOf(false),
+                    ) { hasNotifications, isLowProfile ->
+                        hasNotifications && isLowProfile
+                    }
+                    .distinctUntilChanged()
+            }
+            .logDiffsForTable(
+                tableLogBuffer = tableLogger,
+                columnPrefix = "",
+                columnName = COL_NOTIF_LIGHTS_OUT,
+                initialValue = false,
+            )
 
     override val areaTint: Flow<StatusBarTintColor> =
         darkIconInteractor
@@ -277,19 +290,26 @@
 
     override val shouldHomeStatusBarBeVisible =
         combine(
-            isHomeStatusBarAllowed,
-            keyguardInteractor.isSecureCameraActive,
-            headsUpNotificationInteractor.statusBarHeadsUpStatus,
-        ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
-            // When launching the camera over the lockscreen, the status icons would typically
-            // become visible momentarily before animating out, since we're not yet aware that the
-            // launching camera activity is fullscreen. Even once the activity finishes launching,
-            // it takes a short time before WM decides that the top app wants to hide the icons and
-            // tells us to hide them.
-            // To ensure that this high-visibility animation is smooth, keep the icons hidden during
-            // a camera launch. See b/257292822.
-            headsUpState.isPinned || (isHomeStatusBarAllowed && !isSecureCameraActive)
-        }
+                isHomeStatusBarAllowed,
+                keyguardInteractor.isSecureCameraActive,
+                headsUpNotificationInteractor.statusBarHeadsUpStatus,
+            ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
+                // When launching the camera over the lockscreen, the status icons would typically
+                // become visible momentarily before animating out, since we're not yet aware that
+                // the launching camera activity is fullscreen. Even once the activity finishes
+                // launching, it takes a short time before WM decides that the top app wants to hide
+                // the icons and tells us to hide them. To ensure that this high-visibility
+                // animation is smooth, keep the icons hidden during a camera launch. See
+                // b/257292822.
+                headsUpState.isPinned || (isHomeStatusBarAllowed && !isSecureCameraActive)
+            }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogBuffer = tableLogger,
+                columnPrefix = "",
+                columnName = COL_VISIBLE,
+                initialValue = false,
+            )
 
     private val isAnyChipVisible =
         if (StatusBarNotifChips.isEnabled) {
@@ -313,53 +333,73 @@
 
     override val shouldShowOperatorNameView: Flow<Boolean> =
         combine(
-            shouldHomeStatusBarBeVisible,
-            hideStartSideContentForHeadsUp,
-            homeStatusBarInteractor.visibilityViaDisableFlags,
-            homeStatusBarInteractor.shouldShowOperatorName,
-        ) {
-            shouldStatusBarBeVisible,
-            hideStartSideContentForHeadsUp,
-            visibilityViaDisableFlags,
-            shouldShowOperator ->
-            shouldStatusBarBeVisible &&
-                !hideStartSideContentForHeadsUp &&
-                visibilityViaDisableFlags.isSystemInfoAllowed &&
-                shouldShowOperator
-        }
+                shouldHomeStatusBarBeVisible,
+                hideStartSideContentForHeadsUp,
+                homeStatusBarInteractor.visibilityViaDisableFlags,
+                homeStatusBarInteractor.shouldShowOperatorName,
+            ) {
+                shouldStatusBarBeVisible,
+                hideStartSideContentForHeadsUp,
+                visibilityViaDisableFlags,
+                shouldShowOperator ->
+                shouldStatusBarBeVisible &&
+                    !hideStartSideContentForHeadsUp &&
+                    visibilityViaDisableFlags.isSystemInfoAllowed &&
+                    shouldShowOperator
+            }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogBuffer = tableLogger,
+                columnPrefix = "",
+                columnName = COL_SHOW_OPERATOR_NAME,
+                initialValue = false,
+            )
 
     override val isClockVisible: Flow<VisibilityModel> =
         combine(
-            shouldHomeStatusBarBeVisible,
-            hideStartSideContentForHeadsUp,
-            homeStatusBarInteractor.visibilityViaDisableFlags,
-        ) { shouldStatusBarBeVisible, hideStartSideContentForHeadsUp, visibilityViaDisableFlags ->
-            val showClock =
-                shouldStatusBarBeVisible &&
-                    visibilityViaDisableFlags.isClockAllowed &&
-                    !hideStartSideContentForHeadsUp
-            // Always use View.INVISIBLE here, so that animations work
-            VisibilityModel(showClock.toVisibleOrInvisible(), visibilityViaDisableFlags.animate)
-        }
+                shouldHomeStatusBarBeVisible,
+                hideStartSideContentForHeadsUp,
+                homeStatusBarInteractor.visibilityViaDisableFlags,
+            ) { shouldStatusBarBeVisible, hideStartSideContentForHeadsUp, visibilityViaDisableFlags
+                ->
+                val showClock =
+                    shouldStatusBarBeVisible &&
+                        visibilityViaDisableFlags.isClockAllowed &&
+                        !hideStartSideContentForHeadsUp
+                // Always use View.INVISIBLE here, so that animations work
+                VisibilityModel(showClock.toVisibleOrInvisible(), visibilityViaDisableFlags.animate)
+            }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogBuffer = tableLogger,
+                columnPrefix = COL_PREFIX_CLOCK,
+                initialValue = VisibilityModel(false.toVisibleOrInvisible(), false),
+            )
 
     override val isNotificationIconContainerVisible: Flow<VisibilityModel> =
         combine(
-            shouldHomeStatusBarBeVisible,
-            isAnyChipVisible,
-            homeStatusBarInteractor.visibilityViaDisableFlags,
-        ) { shouldStatusBarBeVisible, anyChipVisible, visibilityViaDisableFlags ->
-            val showNotificationIconContainer =
-                if (anyChipVisible) {
-                    false
-                } else {
-                    shouldStatusBarBeVisible &&
-                        visibilityViaDisableFlags.areNotificationIconsAllowed
-                }
-            VisibilityModel(
-                showNotificationIconContainer.toVisibleOrGone(),
-                visibilityViaDisableFlags.animate,
+                shouldHomeStatusBarBeVisible,
+                isAnyChipVisible,
+                homeStatusBarInteractor.visibilityViaDisableFlags,
+            ) { shouldStatusBarBeVisible, anyChipVisible, visibilityViaDisableFlags ->
+                val showNotificationIconContainer =
+                    if (anyChipVisible) {
+                        false
+                    } else {
+                        shouldStatusBarBeVisible &&
+                            visibilityViaDisableFlags.areNotificationIconsAllowed
+                    }
+                VisibilityModel(
+                    showNotificationIconContainer.toVisibleOrGone(),
+                    visibilityViaDisableFlags.animate,
+                )
+            }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogBuffer = tableLogger,
+                columnPrefix = COL_PREFIX_NOTIF_CONTAINER,
+                initialValue = VisibilityModel(false.toVisibleOrInvisible(), false),
             )
-        }
 
     private val isSystemInfoVisible =
         combine(shouldHomeStatusBarBeVisible, homeStatusBarInteractor.visibilityViaDisableFlags) {
@@ -372,18 +412,19 @@
 
     override val systemInfoCombinedVis =
         combine(isSystemInfoVisible, animations.animationState) { sysInfoVisible, animationState ->
-                HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
-                    sysInfoVisible,
-                    animationState,
-                )
+                SystemInfoCombinedVisibilityModel(sysInfoVisible, animationState)
             }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogBuffer = tableLogger,
+                columnPrefix = COL_PREFIX_SYSTEM_INFO,
+                initialValue =
+                    SystemInfoCombinedVisibilityModel(VisibilityModel(View.VISIBLE, false), Idle),
+            )
             .stateIn(
                 coroutineScope,
                 SharingStarted.WhileSubscribed(),
-                HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel(
-                    VisibilityModel(View.VISIBLE, false),
-                    Idle,
-                ),
+                SystemInfoCombinedVisibilityModel(VisibilityModel(View.VISIBLE, false), Idle),
             )
 
     override val iconBlockList: Flow<List<String>> =
@@ -408,6 +449,19 @@
         HomeStatusBarViewModel.HomeStatusBarViewModelFactory {
         override fun create(displayId: Int): HomeStatusBarViewModelImpl
     }
+
+    companion object {
+        private const val COL_LOCK_TO_OCCLUDED = "Lock->Occluded"
+        private const val COL_ALLOWED_BY_SCENE = "allowedByScene"
+        private const val COL_NOTIF_LIGHTS_OUT = "notifLightsOut"
+        private const val COL_SHOW_OPERATOR_NAME = "showOperatorName"
+        private const val COL_VISIBLE = "visible"
+        private const val COL_PREFIX_CLOCK = "clock"
+        private const val COL_PREFIX_NOTIF_CONTAINER = "notifContainer"
+        private const val COL_PREFIX_SYSTEM_INFO = "systemInfo"
+
+        fun tableLogBufferName(displayId: Int) = "HomeStatusBarViewModel[$displayId]"
+    }
 }
 
 /** Lookup the color for a given view in the status bar */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index 9ff0d18..db5f130 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy.ui.dialog
 
+import android.app.Dialog
+import android.content.Context
 import android.content.Intent
 import android.provider.Settings
 import android.util.Log
@@ -36,6 +38,7 @@
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
+import com.android.settingslib.notification.modes.EnableDndDialogFactory
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
@@ -43,6 +46,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dialog.ui.composable.AlertDialogContent
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.dialog.QSEnableDndDialogMetricsLogger
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
 import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
@@ -61,6 +65,7 @@
 class ModesDialogDelegate
 @Inject
 constructor(
+    val context: Context,
     private val sysuiDialogFactory: SystemUIDialogFactory,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val activityStarter: ActivityStarter,
@@ -72,6 +77,7 @@
 ) : SystemUIDialog.Delegate {
     // NOTE: This should only be accessed/written from the main thread.
     @VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null
+    private val dndDurationDialogLogger by lazy { QSEnableDndDialogMetricsLogger(context) }
 
     override fun createDialog(): SystemUIDialog {
         Assert.isMainThread()
@@ -195,6 +201,26 @@
         activityStarter.startActivity(intent, /* dismissShade= */ true, animationController)
     }
 
+    /**
+     * Special dialog to ask the user for the duration of DND. Not to be confused with the modes
+     * dialog itself.
+     */
+    fun makeDndDurationDialog(): Dialog {
+        val dialog =
+            EnableDndDialogFactory(
+                    context,
+                    R.style.Theme_SystemUI_Dialog,
+                    /* cancelIsNeutral= */ true,
+                    dndDurationDialogLogger,
+                )
+                .createDialog()
+        SystemUIDialog.applyFlags(dialog)
+        SystemUIDialog.setShowForAllUsers(dialog, true)
+        SystemUIDialog.registerDismissListener(dialog)
+        SystemUIDialog.setDialogSize(dialog)
+        return dialog
+    }
+
     companion object {
         private const val TAG = "ModesDialogDelegate"
         private val ZEN_MODE_SETTINGS_INTENT = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 1c13a83..07f1c34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -16,20 +16,16 @@
 
 package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
 
-import android.app.Dialog
 import android.content.Context
 import android.content.Intent
 import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
 import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
-import com.android.settingslib.notification.modes.EnableZenModeDialog
 import com.android.settingslib.notification.modes.ZenMode
 import com.android.settingslib.notification.modes.ZenModeDescriptions
 import com.android.systemui.common.shared.model.asIcon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
 import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger
@@ -54,7 +50,6 @@
     private val dialogDelegate: ModesDialogDelegate,
     private val dialogEventLogger: ModesDialogEventLogger,
 ) {
-    private val zenDialogMetricsLogger = QSZenModeDialogMetricsLogger(context)
     private val zenModeDescriptions = ZenModeDescriptions(context)
 
     // Modes that should be displayed in the dialog
@@ -112,7 +107,7 @@
                                     if (zenModeInteractor.shouldAskForZenDuration(mode)) {
                                         dialogEventLogger.logOpenDurationDialog(mode)
                                         // NOTE: The dialog handles turning on the mode itself.
-                                        val dialog = makeZenModeDialog()
+                                        val dialog = dialogDelegate.makeDndDurationDialog()
                                         dialog.show()
                                     } else {
                                         dialogEventLogger.logModeOn(mode)
@@ -173,20 +168,4 @@
             modeDescription ?: context.getString(R.string.zen_mode_off)
         }
     }
-
-    private fun makeZenModeDialog(): Dialog {
-        val dialog =
-            EnableZenModeDialog(
-                    context,
-                    R.style.Theme_SystemUI_Dialog,
-                    /* cancelIsNeutral= */ true,
-                    zenDialogMetricsLogger,
-                )
-                .createDialog()
-        SystemUIDialog.applyFlags(dialog)
-        SystemUIDialog.setShowForAllUsers(dialog, true)
-        SystemUIDialog.registerDismissListener(dialog)
-        SystemUIDialog.setDialogSize(dialog)
-        return dialog
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 05ee35b..8f5fccd 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -75,8 +75,7 @@
     private static final String[] RESET_EXCEPTION_LIST = new String[] {
             QSHost.TILES_SETTING,
             Settings.Secure.DOZE_ALWAYS_ON,
-            Settings.Secure.MEDIA_CONTROLS_RESUME,
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION
+            Settings.Secure.MEDIA_CONTROLS_RESUME
     };
 
     private final Observer mObserver = new Observer();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
index 9cf02f2..ef147c7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
@@ -21,6 +21,7 @@
 import android.graphics.drawable.Drawable
 import android.media.AudioManager
 import androidx.annotation.DrawableRes
+import com.android.settingslib.R as SettingsR
 import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.RingerMode
@@ -30,8 +31,10 @@
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOf
 
+@SuppressLint("UseCompatLoadingForDrawables")
 class VolumeDialogSliderIconProvider
 @Inject
 constructor(
@@ -40,7 +43,30 @@
     private val audioVolumeInteractor: AudioVolumeInteractor,
 ) {
 
-    @SuppressLint("UseCompatLoadingForDrawables")
+    fun getAudioSharingIcon(isMuted: Boolean): Flow<Drawable> {
+        return flow {
+            val iconRes =
+                if (isMuted) {
+                    R.drawable.ic_volume_media_bt_mute
+                } else {
+                    R.drawable.ic_volume_media_bt
+                }
+            emit(context.getDrawable(iconRes)!!)
+        }
+    }
+
+    fun getCastIcon(isMuted: Boolean): Flow<Drawable> {
+        return flow {
+            val iconRes =
+                if (isMuted) {
+                    SettingsR.drawable.ic_volume_remote_mute
+                } else {
+                    SettingsR.drawable.ic_volume_remote
+                }
+            emit(context.getDrawable(iconRes)!!)
+        }
+    }
+
     fun getStreamIcon(
         stream: Int,
         level: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index 89dd035..a752f1f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -57,12 +57,12 @@
 class VolumeDialogSliderViewModel
 @Inject
 constructor(
+    private val sliderType: VolumeDialogSliderType,
     private val interactor: VolumeDialogSliderInteractor,
     private val visibilityInteractor: VolumeDialogVisibilityInteractor,
     @VolumeDialog private val coroutineScope: CoroutineScope,
     private val volumeDialogSliderIconProvider: VolumeDialogSliderIconProvider,
     private val systemClock: SystemClock,
-    private val sliderType: VolumeDialogSliderType,
     private val logger: VolumeDialogLogger,
 ) {
 
@@ -82,14 +82,24 @@
         model
             .flatMapLatest { streamModel ->
                 with(streamModel) {
-                        volumeDialogSliderIconProvider.getStreamIcon(
-                            stream = stream,
-                            level = level,
-                            levelMin = levelMin,
-                            levelMax = levelMax,
-                            isMuted = muteSupported && muted,
-                            isRoutedToBluetooth = routedToBluetooth,
-                        )
+                        val isMuted = muteSupported && muted
+                        when (sliderType) {
+                            is VolumeDialogSliderType.Stream ->
+                                volumeDialogSliderIconProvider.getStreamIcon(
+                                    stream = sliderType.audioStream,
+                                    level = level,
+                                    levelMin = levelMin,
+                                    levelMax = levelMax,
+                                    isMuted = isMuted,
+                                    isRoutedToBluetooth = routedToBluetooth,
+                                )
+                            is VolumeDialogSliderType.RemoteMediaStream -> {
+                                volumeDialogSliderIconProvider.getCastIcon(isMuted)
+                            }
+                            is VolumeDialogSliderType.AudioSharingStream -> {
+                                volumeDialogSliderIconProvider.getAudioSharingIcon(isMuted)
+                            }
+                        }
                     }
                     .map { icon -> streamModel.toStateModel(icon) }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index 411e06e..0c8dc11 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -293,7 +293,7 @@
                         intent = getSysUiWalletIntent();
                     }
                     startQuickAccessViaIntent(intent, hasCard, activityStarter,
-                            animationController);
+                            animationController, mQuickAccessWalletClient.getUser());
 
                 });
     }
@@ -323,7 +323,8 @@
     private void startQuickAccessViaIntent(Intent intent,
             boolean hasCard,
             ActivityStarter activityStarter,
-            ActivityTransitionAnimator.Controller animationController) {
+            ActivityTransitionAnimator.Controller animationController,
+            UserHandle user) {
         if (hasCard) {
             activityStarter.startActivity(intent, true /* dismissShade */,
                     animationController, true /* showOverLockscreenWhenLocked */);
@@ -331,7 +332,9 @@
             activityStarter.postStartActivityDismissingKeyguard(
                     intent,
                     /* delay= */ 0,
-                    animationController);
+                    animationController,
+                    null,
+                    user);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 111492c..18e1b6e 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -163,15 +163,13 @@
 
                     if (mKeyguardStateController.isUnlocked()) {
                         mUiEventLogger.log(WalletUiEvent.QAW_SHOW_ALL);
-                        mActivityStarter.startActivity(
-                                mWalletClient.createWalletIntent(), true);
+                        startWalletActivity();
                         finish();
                     } else {
                         mUiEventLogger.log(WalletUiEvent.QAW_UNLOCK_FROM_SHOW_ALL_BUTTON);
                         mKeyguardDismissUtil.executeWhenUnlocked(() -> {
                             mUiEventLogger.log(WalletUiEvent.QAW_SHOW_ALL);
-                            mActivityStarter.startActivity(
-                                    mWalletClient.createWalletIntent(), true);
+                            startWalletActivity();
                             finish();
                             return false;
                         }, false, true);
@@ -193,6 +191,11 @@
                 });
     }
 
+    private void startWalletActivity() {
+        mActivityStarter.startActivity(mWalletClient.createWalletIntent(), true,
+                null, true, mWalletClient.getUser());
+    }
+
     @Override
     protected void onStart() {
         super.onStart();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index bac2c47..1729a4d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -55,6 +55,7 @@
 import com.android.systemui.plugins.clocks.ThemeConfig
 import com.android.systemui.plugins.clocks.ZenData
 import com.android.systemui.plugins.clocks.ZenData.ZenMode
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.BatteryController
@@ -131,6 +132,7 @@
     @Mock private lateinit var parentView: View
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var powerInteractor: PowerInteractor
 
     @Mock private lateinit var zenModeController: ZenModeController
     private var zenModeControllerCallback: ZenModeController.Callback? = null
@@ -178,6 +180,7 @@
                 zenModeController,
                 zenModeInteractor,
                 userTracker,
+                powerInteractor,
             )
         underTest.clock = clock
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 82bf5e2..a3c3d2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -205,6 +205,7 @@
                 biometricSettingsRepository = biometricSettingsRepository,
                 backgroundDispatcher = testDispatcher,
                 appContext = mContext,
+                accessibilityManager = mock(),
                 communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 sceneInteractor = { kosmos.sceneInteractor },
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 111d819..21519b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -322,6 +322,7 @@
                 biometricSettingsRepository = biometricSettingsRepository,
                 backgroundDispatcher = testDispatcher,
                 appContext = mContext,
+                accessibilityManager = mock(),
                 communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 sceneInteractor = { kosmos.sceneInteractor },
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index 8c00047..caf08ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -325,6 +325,7 @@
                 biometricSettingsRepository = biometricSettingsRepository,
                 backgroundDispatcher = testDispatcher,
                 appContext = mContext,
+                accessibilityManager = mock(),
                 communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 sceneInteractor = { kosmos.sceneInteractor },
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 0b2b867..b5a2271 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -294,6 +294,7 @@
                         biometricSettingsRepository = biometricSettingsRepository,
                         backgroundDispatcher = kosmos.testDispatcher,
                         appContext = mContext,
+                        accessibilityManager = mock(),
                         communalSettingsInteractor = kosmos.communalSettingsInteractor,
                         sceneInteractor = { kosmos.sceneInteractor },
                     ),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index 3ddd4b5..2815b9769 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -41,13 +41,11 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
-import android.provider.Settings
 import android.service.notification.StatusBarNotification
 import android.testing.TestableLooper.RunWithLooper
 import androidx.media.utils.MediaConstants
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Flags
 import com.android.systemui.InstanceIdSequenceFake
@@ -65,10 +63,7 @@
 import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
 import com.android.systemui.media.controls.shared.mediaLogger
 import com.android.systemui.media.controls.shared.mockMediaLogger
-import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
-import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
 import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.media.controls.util.fakeMediaControllerFactory
@@ -76,7 +71,6 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.testKosmos
-import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -95,12 +89,10 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoSession
 import org.mockito.junit.MockitoJUnit
@@ -126,10 +118,6 @@
 private const val USER_ID = 0
 private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
 
-private fun <T> anyObject(): T {
-    return Mockito.anyObject<T>()
-}
-
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWithLooper(setAsMainLooper = true)
@@ -168,8 +156,6 @@
     lateinit var remoteCastNotification: StatusBarNotification
     @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
     private val clock = FakeSystemClock()
-    @Mock private lateinit var tunerService: TunerService
-    @Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
     @Captor lateinit var stateCallbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
     @Captor lateinit var sessionCallbackCaptor: ArgumentCaptor<(String) -> Unit>
     @Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
@@ -197,13 +183,6 @@
     private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
     private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
 
-    private val originalSmartspaceSetting =
-        Settings.Secure.getInt(
-            context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
-            1,
-        )
-
     private lateinit var staticMockSession: MockitoSession
 
     @Before
@@ -219,11 +198,6 @@
         backgroundExecutor = FakeExecutor(clock)
         uiExecutor = FakeExecutor(clock)
         smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
-        Settings.Secure.putInt(
-            context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
-            1,
-        )
         mediaDataManager =
             LegacyMediaDataManagerImpl(
                 context = context,
@@ -246,7 +220,6 @@
                 useMediaResumption = true,
                 useQsMediaPlayer = true,
                 systemClock = clock,
-                tunerService = tunerService,
                 mediaFlags = kosmos.mediaFlags,
                 logger = logger,
                 smartspaceManager = smartspaceManager,
@@ -254,8 +227,6 @@
                 mediaDataLoader = { kosmos.mediaDataLoader },
                 mediaLogger = kosmos.mediaLogger,
             )
-        verify(tunerService)
-            .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
         verify(mediaTimeoutListener).stateCallback = capture(stateCallbackCaptor)
         verify(mediaTimeoutListener).sessionCallback = capture(sessionCallbackCaptor)
         session = MediaSession(context, "MediaDataManagerTestSession")
@@ -332,11 +303,6 @@
         staticMockSession.finishMocking()
         session.release()
         mediaDataManager.destroy()
-        Settings.Secure.putInt(
-            context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
-            originalSmartspaceSetting,
-        )
     }
 
     @Test
@@ -1236,272 +1202,6 @@
     }
 
     @Test
-    fun testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListener() {
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        verify(logger).getNewInstanceId()
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    SmartspaceMediaData(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = true,
-                        packageName = PACKAGE_NAME,
-                        cardAction = mediaSmartspaceBaseAction,
-                        recommendations = validRecommendationList,
-                        dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListener() {
-        whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        verify(logger).getNewInstanceId()
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = true,
-                        dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() {
-        val recommendationExtras =
-            Bundle().apply {
-                putString("package_name", PACKAGE_NAME)
-                putParcelable("dismiss_intent", null)
-            }
-        whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
-        whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
-        whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        verify(logger).getNewInstanceId()
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = true,
-                        dismissIntent = null,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf())
-        verify(logger, never()).getNewInstanceId()
-        verify(listener, never())
-            .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        verify(logger).getNewInstanceId()
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf())
-        uiExecutor.advanceClockToLast()
-        uiExecutor.runAllReady()
-
-        verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
-        verifyNoMoreInteractions(logger)
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
-        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    SmartspaceMediaData(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = true,
-                        packageName = PACKAGE_NAME,
-                        cardAction = mediaSmartspaceBaseAction,
-                        recommendations = validRecommendationList,
-                        dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
-        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-        val extras =
-            Bundle().apply {
-                putString("package_name", PACKAGE_NAME)
-                putParcelable("dismiss_intent", DISMISS_INTENT)
-                putString(EXTRA_KEY_TRIGGER_SOURCE, EXTRA_VALUE_TRIGGER_PERIODIC)
-            }
-        whenever(mediaSmartspaceBaseAction.extras).thenReturn(extras)
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    SmartspaceMediaData(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = false,
-                        packageName = PACKAGE_NAME,
-                        cardAction = mediaSmartspaceBaseAction,
-                        recommendations = validRecommendationList,
-                        dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
-        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf())
-        uiExecutor.advanceClockToLast()
-        uiExecutor.runAllReady()
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    SmartspaceMediaData(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = false,
-                        packageName = PACKAGE_NAME,
-                        cardAction = mediaSmartspaceBaseAction,
-                        recommendations = validRecommendationList,
-                        dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-        verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
-    }
-
-    @Test
-    fun testSetRecommendationInactive_notifiesListeners() {
-        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        mediaDataManager.setRecommendationInactive(KEY_MEDIA_SMARTSPACE)
-        uiExecutor.advanceClockToLast()
-        uiExecutor.runAllReady()
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    SmartspaceMediaData(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = false,
-                        packageName = PACKAGE_NAME,
-                        cardAction = mediaSmartspaceBaseAction,
-                        recommendations = validRecommendationList,
-                        dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
-        // WHEN media recommendation setting is off
-        Settings.Secure.putInt(
-            context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
-            0,
-        )
-        tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-
-        // THEN smartspace signal is ignored
-        verify(listener, never())
-            .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
-    }
-
-    @Test
-    fun testMediaRecommendationDisabled_removesSmartspaceData() {
-        // GIVEN a media recommendation card is present
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), anyBoolean())
-
-        // WHEN the media recommendation setting is turned off
-        Settings.Secure.putInt(
-            context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
-            0,
-        )
-        tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
-
-        // THEN listeners are notified
-        uiExecutor.advanceClockToLast()
-        foregroundExecutor.advanceClockToLast()
-        uiExecutor.runAllReady()
-        foregroundExecutor.runAllReady()
-        verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
-    }
-
-    @Test
     fun testOnMediaDataChanged_updatesLastActiveTime() {
         val currentTime = clock.elapsedRealtime()
         addNotificationAndLoad()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index e5483c0..b9ebce8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -42,13 +42,11 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
-import android.provider.Settings
 import android.service.notification.StatusBarNotification
 import android.testing.TestableLooper.RunWithLooper
 import androidx.media.utils.MediaConstants
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Flags
 import com.android.systemui.InstanceIdSequenceFake
@@ -71,21 +69,16 @@
 import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
 import com.android.systemui.media.controls.shared.mediaLogger
 import com.android.systemui.media.controls.shared.mockMediaLogger
-import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
-import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
 import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.media.controls.util.fakeMediaControllerFactory
 import com.android.systemui.media.controls.util.mediaFlags
-import com.android.systemui.plugins.activityStarter
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.statusbar.notificationLockscreenUserManager
 import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.settings.fakeSettings
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -102,11 +95,9 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoSession
 import org.mockito.junit.MockitoJUnit
 import org.mockito.kotlin.any
@@ -133,10 +124,6 @@
 private const val USER_ID = 0
 private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
 
-private fun <T> anyObject(): T {
-    return Mockito.anyObject<T>()
-}
-
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWithLooper(setAsMainLooper = true)
@@ -146,7 +133,6 @@
     private val kosmos = testKosmos().apply { mediaLogger = mockMediaLogger }
     private val testDispatcher = kosmos.testDispatcher
     private val testScope = kosmos.testScope
-    private val settings = kosmos.fakeSettings
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
     @Mock lateinit var controller: MediaController
@@ -201,7 +187,6 @@
     }
 
     private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
-    private val activityStarter = kosmos.activityStarter
     private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
     private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
     private val mediaFilterRepository = kosmos.mediaFilterRepository
@@ -209,13 +194,6 @@
 
     private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
 
-    private val originalSmartspaceSetting =
-        Settings.Secure.getInt(
-            context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
-            1,
-        )
-
     private lateinit var staticMockSession: MockitoSession
 
     @Before
@@ -231,11 +209,6 @@
         backgroundExecutor = FakeExecutor(clock)
         uiExecutor = FakeExecutor(clock)
         smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
-        Settings.Secure.putInt(
-            context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
-            1,
-        )
         mediaDataProcessor =
             MediaDataProcessor(
                 context = context,
@@ -248,12 +221,10 @@
                 mediaControllerFactory = mediaControllerFactory,
                 broadcastDispatcher = broadcastDispatcher,
                 dumpManager = dumpManager,
-                activityStarter = activityStarter,
                 smartspaceMediaDataProvider = smartspaceMediaDataProvider,
                 useMediaResumption = true,
                 useQsMediaPlayer = true,
                 systemClock = clock,
-                secureSettings = settings,
                 mediaFlags = kosmos.mediaFlags,
                 logger = logger,
                 smartspaceManager = smartspaceManager,
@@ -355,11 +326,6 @@
         staticMockSession.finishMocking()
         session.release()
         mediaDataProcessor.destroy()
-        Settings.Secure.putInt(
-            context.contentResolver,
-            Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
-            originalSmartspaceSetting,
-        )
     }
 
     @Test
@@ -1255,264 +1221,6 @@
     }
 
     @Test
-    fun testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListener() {
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        verify(logger).getNewInstanceId()
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    SmartspaceMediaData(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = true,
-                        packageName = PACKAGE_NAME,
-                        cardAction = mediaSmartspaceBaseAction,
-                        recommendations = validRecommendationList,
-                        dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListener() {
-        whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        verify(logger).getNewInstanceId()
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    SmartspaceMediaData(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = true,
-                        dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() {
-        val recommendationExtras =
-            Bundle().apply {
-                putString("package_name", PACKAGE_NAME)
-                putParcelable("dismiss_intent", null)
-            }
-        whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
-        whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
-        whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        verify(logger).getNewInstanceId()
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    SmartspaceMediaData(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = true,
-                        dismissIntent = null,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf())
-        verify(logger, never()).getNewInstanceId()
-        verify(listener, never())
-            .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        verify(logger).getNewInstanceId()
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf())
-        uiExecutor.advanceClockToLast()
-        uiExecutor.runAllReady()
-
-        verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
-        verifyNoMoreInteractions(logger)
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
-        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    SmartspaceMediaData(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = true,
-                        packageName = PACKAGE_NAME,
-                        cardAction = mediaSmartspaceBaseAction,
-                        recommendations = validRecommendationList,
-                        dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
-        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-        val extras =
-            Bundle().apply {
-                putString("package_name", PACKAGE_NAME)
-                putParcelable("dismiss_intent", DISMISS_INTENT)
-                putString(EXTRA_KEY_TRIGGER_SOURCE, EXTRA_VALUE_TRIGGER_PERIODIC)
-            }
-        whenever(mediaSmartspaceBaseAction.extras).thenReturn(extras)
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    SmartspaceMediaData(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = false,
-                        packageName = PACKAGE_NAME,
-                        cardAction = mediaSmartspaceBaseAction,
-                        recommendations = validRecommendationList,
-                        dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
-        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf())
-        uiExecutor.advanceClockToLast()
-        uiExecutor.runAllReady()
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    SmartspaceMediaData(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = false,
-                        packageName = PACKAGE_NAME,
-                        cardAction = mediaSmartspaceBaseAction,
-                        recommendations = validRecommendationList,
-                        dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-        verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
-    }
-
-    @Test
-    fun testSetRecommendationInactive_notifiesListeners() {
-        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        val instanceId = instanceIdSequence.lastInstanceId
-
-        mediaDataProcessor.setRecommendationInactive(KEY_MEDIA_SMARTSPACE)
-        uiExecutor.advanceClockToLast()
-        uiExecutor.runAllReady()
-
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(
-                eq(KEY_MEDIA_SMARTSPACE),
-                eq(
-                    SmartspaceMediaData(
-                        targetId = KEY_MEDIA_SMARTSPACE,
-                        isActive = false,
-                        packageName = PACKAGE_NAME,
-                        cardAction = mediaSmartspaceBaseAction,
-                        recommendations = validRecommendationList,
-                        dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
-                        instanceId = InstanceId.fakeInstanceId(instanceId),
-                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
-                    )
-                ),
-                eq(false),
-            )
-    }
-
-    @Test
-    fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
-        // WHEN media recommendation setting is off
-        settings.putInt(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
-        testScope.runCurrent()
-
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-
-        // THEN smartspace signal is ignored
-        verify(listener, never())
-            .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
-    }
-
-    @Test
-    fun testMediaRecommendationDisabled_removesSmartspaceData() {
-        // GIVEN a media recommendation card is present
-        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-        verify(listener)
-            .onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), anyBoolean())
-
-        // WHEN the media recommendation setting is turned off
-        settings.putInt(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
-        testScope.runCurrent()
-
-        // THEN listeners are notified
-        uiExecutor.advanceClockToLast()
-        foregroundExecutor.advanceClockToLast()
-        uiExecutor.runAllReady()
-        foregroundExecutor.runAllReady()
-        verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
-    }
-
-    @Test
     fun testOnMediaDataChanged_updatesLastActiveTime() {
         val currentTime = clock.elapsedRealtime()
         addNotificationAndLoad()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 38ddb3e..a02d333 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -30,7 +30,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -1425,8 +1424,7 @@
         HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList(
                 ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, BOUNCER,
                 ScrimState.DREAMING, ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR,
-                ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.AUTH_SCRIMMED,
-                ScrimState.AUTH_SCRIMMED_SHADE, ScrimState.GLANCEABLE_HUB,
+                ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.GLANCEABLE_HUB,
                 ScrimState.GLANCEABLE_HUB_OVER_DREAM));
 
         for (ScrimState state : ScrimState.values()) {
@@ -1451,79 +1449,6 @@
     }
 
     @Test
-    public void testAuthScrim_setClipQSScrimTrue_notifScrimOpaque_whenShadeFullyExpanded() {
-        // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
-        // with the camera app occluding the keyguard)
-        mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
-        mScrimController.setClipsQsScrim(true);
-        mScrimController.setRawPanelExpansionFraction(1);
-        // notifications scrim alpha change require calling setQsPosition
-        mScrimController.setQsPosition(0, 300);
-        finishAnimationsImmediately();
-
-        // WHEN the user triggers the auth bouncer
-        mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
-        finishAnimationsImmediately();
-
-        assertEquals("Behind scrim should be opaque",
-                mScrimBehind.getViewAlpha(), 1, 0.0);
-        assertEquals("Notifications scrim should be opaque",
-                mNotificationsScrim.getViewAlpha(), 1, 0.0);
-
-        assertScrimTinted(Map.of(
-                mScrimInFront, true,
-                mScrimBehind, true,
-                mNotificationsScrim, false
-        ));
-    }
-
-
-    @Test
-    public void testAuthScrim_setClipQSScrimFalse_notifScrimOpaque_whenShadeFullyExpanded() {
-        // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
-        // with the camera app occluding the keyguard)
-        mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
-        mScrimController.setClipsQsScrim(false);
-        mScrimController.setRawPanelExpansionFraction(1);
-        // notifications scrim alpha change require calling setQsPosition
-        mScrimController.setQsPosition(0, 300);
-        finishAnimationsImmediately();
-
-        // WHEN the user triggers the auth bouncer
-        mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
-        finishAnimationsImmediately();
-
-        assertEquals("Behind scrim should be opaque",
-                mScrimBehind.getViewAlpha(), 1, 0.0);
-        assertEquals("Notifications scrim should be opaque",
-                mNotificationsScrim.getViewAlpha(), 1, 0.0);
-
-        assertScrimTinted(Map.of(
-                mScrimInFront, true,
-                mScrimBehind, true,
-                mNotificationsScrim, false
-        ));
-    }
-
-    @Test
-    public void testAuthScrimKeyguard() {
-        // GIVEN device is on the keyguard
-        mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
-        finishAnimationsImmediately();
-
-        // WHEN the user triggers the auth bouncer
-        mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED);
-        finishAnimationsImmediately();
-
-        // THEN the front scrim is updated and the KEYGUARD scrims are the same as the
-        // KEYGUARD scrim state
-        assertScrimAlpha(Map.of(
-                mScrimInFront, SEMI_TRANSPARENT,
-                mScrimBehind, SEMI_TRANSPARENT,
-                mNotificationsScrim, TRANSPARENT));
-    }
-
-    @Test
     public void testScrimsVisible_whenShadeVisible() {
         mScrimController.setClipsQsScrim(true);
         mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
index 733e2ed..8e97c86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.wallet.controller;
 
 import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
@@ -35,6 +36,7 @@
 import android.app.PendingIntent;
 import android.app.role.RoleManager;
 import android.content.Intent;
+import android.os.UserHandle;
 import android.platform.test.annotations.EnableFlags;
 import android.service.quickaccesswallet.GetWalletCardsRequest;
 import android.service.quickaccesswallet.QuickAccessWalletClient;
@@ -59,7 +61,6 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
 
 import java.util.List;
 
@@ -98,6 +99,7 @@
         when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true);
         when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
         when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true);
+        when(mQuickAccessWalletClient.getUser()).thenReturn(UserHandle.of(0));
         mClock.setElapsedRealtime(100L);
 
         doAnswer(invocation -> {
@@ -269,7 +271,8 @@
     public void getQuickAccessUiIntent_noCards_noPendingIntent_startsWalletActivity() {
         mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, false);
         verify(mActivityStarter).postStartActivityDismissingKeyguard(mIntentCaptor.capture(), eq(0),
-                any(ActivityTransitionAnimator.Controller.class));
+                any(ActivityTransitionAnimator.Controller.class), eq(null),
+                eq(UserHandle.of(0)));
         Intent intent = mIntentCaptor.getValue();
         assertEquals(intent.getAction(), Intent.ACTION_VIEW);
         assertEquals(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 7508838..fab7922 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -358,6 +358,8 @@
     private Display mDefaultDisplay;
     @Mock
     private Lazy<ViewCapture> mLazyViewCapture;
+    @Mock
+    private SyncTransactionQueue mSyncQueue;
 
     private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
     private ShadeInteractor mShadeInteractor;
@@ -400,8 +402,6 @@
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             doReturn(true).when(mTransitions).isRegistered();
         }
-        mTaskViewRepository = new TaskViewRepository();
-        mTaskViewTransitions = new TaskViewTransitions(mTransitions, mTaskViewRepository);
 
         mTestableLooper = TestableLooper.get(this);
 
@@ -518,6 +518,9 @@
                 Optional.empty(),
                 Optional.empty(),
                 syncExecutor);
+        mTaskViewRepository = new TaskViewRepository();
+        mTaskViewTransitions = new TaskViewTransitions(mTransitions, mTaskViewRepository,
+                mShellTaskOrganizer, mSyncQueue);
         mBubbleProperties = new FakeBubbleProperties();
         mBubbleController = new TestableBubbleController(
                 mContext,
@@ -542,6 +545,7 @@
                 mock(DragAndDropController.class),
                 syncExecutor,
                 mock(Handler.class),
+                mTaskViewRepository,
                 mTaskViewTransitions,
                 mTransitions,
                 mock(SyncTransactionQueue.class),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
index 3b1199a..ba64ed7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
@@ -18,6 +18,7 @@
 
 import android.app.admin.devicePolicyManager
 import android.content.applicationContext
+import android.view.accessibility.AccessibilityManager
 import com.android.internal.widget.lockPatternUtils
 import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
 import com.android.systemui.animation.dialogTransitionAnimator
@@ -54,6 +55,7 @@
         dockManager = dockManager,
         biometricSettingsRepository = biometricSettingsRepository,
         communalSettingsInteractor = communalSettingsInteractor,
+        accessibilityManager = mock<AccessibilityManager>(),
         backgroundDispatcher = testDispatcher,
         appContext = applicationContext,
         sceneInteractor = { sceneInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index abbfa93..1c0f97d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -56,9 +56,11 @@
         aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
         aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
         aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
+        aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel,
         dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
         dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
         dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+        dozingToPrimaryBouncerTransitionViewModel = dozingToPrimaryBouncerTransitionViewModel,
         dreamingToAodTransitionViewModel = dreamingToAodTransitionViewModel,
         dreamingToGoneTransitionViewModel = dreamingToGoneTransitionViewModel,
         dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt
new file mode 100644
index 0000000..93da1f3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.lightRevealScrimInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.lightRevealScrimViewModel by Fixture {
+    LightRevealScrimViewModel(interactor = lightRevealScrimInteractor)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
index 174e653..fcaad6b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
@@ -31,9 +31,7 @@
 import com.android.systemui.media.controls.util.fakeMediaControllerFactory
 import com.android.systemui.media.controls.util.mediaFlags
 import com.android.systemui.media.controls.util.mediaUiEventLogger
-import com.android.systemui.plugins.activityStarter
 import com.android.systemui.util.Utils
-import com.android.systemui.util.settings.fakeSettings
 import com.android.systemui.util.time.systemClock
 
 val Kosmos.mediaDataProcessor by
@@ -49,12 +47,10 @@
             mediaControllerFactory = fakeMediaControllerFactory,
             broadcastDispatcher = broadcastDispatcher,
             dumpManager = dumpManager,
-            activityStarter = activityStarter,
             smartspaceMediaDataProvider = SmartspaceMediaDataProvider(),
             useMediaResumption = Utils.useMediaResumption(applicationContext),
             useQsMediaPlayer = Utils.useQsMediaPlayer(applicationContext),
             systemClock = systemClock,
-            secureSettings = fakeSettings,
             mediaFlags = mediaFlags,
             logger = mediaUiEventLogger,
             smartspaceManager = SmartspaceManager(applicationContext),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
index 3c37101..13cbddf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger
 import javax.inject.Provider
 
 val Kosmos.modesTileUserActionInteractor: ModesTileUserActionInteractor by
@@ -28,5 +29,6 @@
             qsTileIntentUserInputHandler,
             Provider { modesDialogDelegate }.get(),
             zenModeInteractor,
+            modesDialogEventLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 0eca818..609f97d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -4,6 +4,7 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.haptics.msdl.msdlPlayer
+import com.android.systemui.keyguard.ui.viewmodel.lightRevealScrimViewModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.power.domain.interactor.powerInteractor
@@ -19,6 +20,7 @@
 import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
+import com.android.systemui.wallpapers.ui.viewmodel.wallpaperViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import org.mockito.kotlin.mock
 
@@ -96,6 +98,8 @@
                 hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
                 view = view,
                 motionEventHandlerReceiver = motionEventHandlerReceiver,
+                lightRevealScrim = lightRevealScrimViewModel,
+                wallpaperViewModel = wallpaperViewModel,
             )
     }
 }
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 60e092c..8461da7 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
@@ -28,9 +28,11 @@
 import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.aodToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.dozingToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dozingToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.goneToAodTransitionViewModel
@@ -78,9 +80,11 @@
         aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
         aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
         aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
+        aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel,
         dozingToGlanceableHubTransitionViewModel = dozingToGlanceableHubTransitionViewModel,
         dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
         dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+        dozingToPrimaryBouncerTransitionViewModel = dozingToPrimaryBouncerTransitionViewModel,
         dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
         goneToAodTransitionViewModel = goneToAodTransitionViewModel,
         goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index db7e31b..f8bf3c3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.table.tableLogBufferFactory
 import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -39,6 +40,7 @@
     Kosmos.Fixture {
         HomeStatusBarViewModelImpl(
             testableContext.displayId,
+            tableLogBufferFactory,
             homeStatusBarInteractor,
             homeStatusBarIconBlockListInteractor,
             lightsOutInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
index 6c98d19..ef043e0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy.ui.dialog
 
+import android.content.mockedContext
 import com.android.systemui.animation.dialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.mainCoroutineContext
@@ -30,6 +31,7 @@
 var Kosmos.modesDialogDelegate: ModesDialogDelegate by
     Kosmos.Fixture {
         ModesDialogDelegate(
+            mockedContext,
             systemUIDialogFactory,
             dialogTransitionAnimator,
             activityStarter,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
index 1d8c891..ddb9a3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
@@ -26,13 +26,11 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.user.data.repository.userRepository
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-@OptIn(ExperimentalCoroutinesApi::class)
 val Kosmos.wallpaperRepository by Fixture {
     WallpaperRepositoryImpl(
         context = applicationContext,
-        scope = testScope,
+        scope = testScope.backgroundScope,
         bgDispatcher = testDispatcher,
         broadcastDispatcher = broadcastDispatcher,
         userRepository = userRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt
new file mode 100644
index 0000000..bb6596e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.wallpapers.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.wallpapers.domain.interactor.wallpaperInteractor
+
+val Kosmos.wallpaperViewModel by Fixture { WallpaperViewModel(interactor = wallpaperInteractor) }
diff --git a/packages/SystemUI/utils/Android.bp b/packages/SystemUI/utils/Android.bp
index 1ef3816..1efb11b 100644
--- a/packages/SystemUI/utils/Android.bp
+++ b/packages/SystemUI/utils/Android.bp
@@ -29,4 +29,5 @@
         "kotlin-stdlib",
         "kotlinx_coroutines",
     ],
+    kotlincflags: ["-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"],
 }
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
index 5f8c660..4cfb7d5 100644
--- a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalTypeInference::class)
+@file:OptIn(ExperimentalTypeInference::class)
 
 package com.android.systemui.utils.coroutines.flow
 
 import kotlin.experimental.ExperimentalTypeInference
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.FlowCollector
 import kotlinx.coroutines.flow.conflate
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index f8315fe..383e75b 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -115,6 +115,7 @@
 android.util.Xml
 
 android.util.proto.EncodedBuffer
+android.util.proto.ProtoFieldFilter
 android.util.proto.ProtoInputStream
 android.util.proto.ProtoOutputStream
 android.util.proto.ProtoParseException
diff --git a/ravenwood/texts/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt
index 3ec3e3c..27223d8b 100644
--- a/ravenwood/texts/ravenwood-standard-options.txt
+++ b/ravenwood/texts/ravenwood-standard-options.txt
@@ -5,6 +5,8 @@
 # Keep all classes / methods / fields, but make the methods throw.
 --default-throw
 
+--delete-finals
+
 # Uncomment below lines to enable each feature.
 
 #--default-method-call-hook
diff --git a/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt b/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt
index 001943c..9c46a16 100644
--- a/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt
+++ b/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt
@@ -2,6 +2,8 @@
 
 --debug
 
+--delete-finals
+
 # Uncomment below lines to enable each feature.
 
 #--default-method-call-hook
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index cc704b2..9859475 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -411,6 +411,8 @@
             stats = stats,
             enablePreTrace = options.enablePreTrace.get,
             enablePostTrace = options.enablePostTrace.get,
+            deleteClassFinals = options.deleteFinals.get,
+            deleteMethodFinals = options.deleteFinals.get,
         )
         outVisitor = BaseAdapter.getVisitor(
             classInternalName, classes, outVisitor, filter,
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 55e853e..ae9276f 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -106,6 +106,8 @@
 
         var cleanUpOnError: SetOnce<Boolean> = SetOnce(false),
 
+        var deleteFinals: SetOnce<Boolean> = SetOnce(false),
+
         var enableClassChecker: SetOnce<Boolean> = SetOnce(false),
         var enablePreTrace: SetOnce<Boolean> = SetOnce(false),
         var enablePostTrace: SetOnce<Boolean> = SetOnce(false),
@@ -218,6 +220,8 @@
                         "--gen-keep-all-file" ->
                             ret.inputJarAsKeepAllFile.set(nextArg())
 
+                        "--delete-finals" -> ret.deleteFinals.set(true)
+
                         // Following options are for debugging.
                         "--enable-class-checker" -> ret.enableClassChecker.set(true)
                         "--no-class-checker" -> ret.enableClassChecker.set(false)
@@ -293,6 +297,7 @@
               defaultMethodCallHook=$defaultMethodCallHook,
               policyOverrideFiles=${policyOverrideFiles.toTypedArray().contentToString()},
               defaultPolicy=$defaultPolicy,
+              deleteFinals=$deleteFinals,
               cleanUpOnError=$cleanUpOnError,
               enableClassChecker=$enableClassChecker,
               enablePreTrace=$enablePreTrace,
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index 261ef59c..a08d1d6 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -50,7 +50,13 @@
         val errors: HostStubGenErrors,
         val stats: HostStubGenStats?,
         val enablePreTrace: Boolean,
-        val enablePostTrace: Boolean
+        val enablePostTrace: Boolean,
+        val deleteClassFinals: Boolean,
+        val deleteMethodFinals: Boolean,
+        // We don't remove finals from fields, because final fields have a stronger memory
+        // guarantee than non-final fields, see:
+        // https://docs.oracle.com/javase/specs/jls/se22/html/jls-17.html#jls-17.5
+        // i.e. changing a final field to non-final _could_ result in different behavior.
     )
 
     protected lateinit var currentPackageName: String
@@ -58,14 +64,33 @@
     protected var redirectionClass: String? = null
     protected lateinit var classPolicy: FilterPolicyWithReason
 
+    private fun isEnum(access: Int): Boolean {
+        return (access and Opcodes.ACC_ENUM) != 0
+    }
+
+    protected fun modifyClassAccess(access: Int): Int {
+        if (options.deleteClassFinals && !isEnum(access)) {
+            return access and Opcodes.ACC_FINAL.inv()
+        }
+        return access
+    }
+
+    protected fun modifyMethodAccess(access: Int): Int {
+        if (options.deleteMethodFinals) {
+            return access and Opcodes.ACC_FINAL.inv()
+        }
+        return access
+    }
+
     override fun visit(
         version: Int,
-        access: Int,
+        origAccess: Int,
         name: String,
         signature: String?,
         superName: String?,
         interfaces: Array<String>,
     ) {
+        val access = modifyClassAccess(origAccess)
         super.visit(version, access, name, signature, superName, interfaces)
         currentClassName = name
         currentPackageName = getPackageNameFromFullClassName(name)
@@ -130,13 +155,14 @@
         }
     }
 
-    override fun visitMethod(
-        access: Int,
+    final override fun visitMethod(
+        origAccess: Int,
         name: String,
         descriptor: String,
         signature: String?,
         exceptions: Array<String>?,
     ): MethodVisitor? {
+        val access = modifyMethodAccess(origAccess)
         if (skipMemberModificationNestCount > 0) {
             return super.visitMethod(access, name, descriptor, signature, exceptions)
         }
@@ -176,6 +202,7 @@
                 if (newAccess == NOT_COMPATIBLE) {
                     return null
                 }
+                newAccess = modifyMethodAccess(newAccess)
 
                 log.v(
                     "Emitting %s.%s%s as %s %s", currentClassName, name, descriptor,
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index 567a69e..70e7d46 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -51,12 +51,13 @@
 
     override fun visit(
         version: Int,
-        access: Int,
+        origAccess: Int,
         name: String,
         signature: String?,
         superName: String?,
         interfaces: Array<String>
     ) {
+        val access = modifyClassAccess(origAccess)
         super.visit(version, access, name, signature, superName, interfaces)
 
         classLoadHooks = filter.getClassLoadHooks(currentClassName)
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 5e5ca62..b009b09 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -7,6 +7,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestClassLoadHook
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
   public abstract java.lang.String value();
     descriptor: ()Ljava/lang/String;
     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -30,6 +32,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestIgnore
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestIgnore.java"
 RuntimeVisibleAnnotations:
@@ -50,6 +54,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestKeep
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestKeep.java"
 RuntimeVisibleAnnotations:
@@ -70,6 +76,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestRedirect
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestRedirect.java"
 RuntimeVisibleAnnotations:
@@ -90,6 +98,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestRedirectionClass
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
   public abstract java.lang.String value();
     descriptor: ()Ljava/lang/String;
     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -113,6 +123,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestRemove
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestRemove.java"
 RuntimeVisibleAnnotations:
@@ -133,6 +145,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestStaticInitializerKeep
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestStaticInitializerKeep.java"
 RuntimeVisibleAnnotations:
@@ -153,6 +167,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestSubstitute
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
   public abstract java.lang.String suffix();
     descriptor: ()Ljava/lang/String;
     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -176,6 +192,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestThrow
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestThrow.java"
 RuntimeVisibleAnnotations:
@@ -196,6 +214,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassKeep
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestWholeClassKeep.java"
 RuntimeVisibleAnnotations:
@@ -216,6 +236,8 @@
   this_class: #x                          // android/hosttest/annotation/tests/HostSideTestSuppress
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestSuppress.java"
 RuntimeVisibleAnnotations:
@@ -232,6 +254,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 3
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -273,6 +297,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 3
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -314,6 +340,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 0, attributes: 3
+Constant pool:
+{
 }
 SourceFile: "IPretendingAidl.java"
 NestMembers:
@@ -331,6 +359,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/R$Nested
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 2, attributes: 3
+Constant pool:
+{
   public static int[] ARRAY;
     descriptor: [I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -376,6 +406,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/R
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 3
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.R();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -396,13 +428,15 @@
   public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
   Compiled from "TinyFrameworkAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
   minor version: 0
   major version: 65
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 9, attributes: 2
+Constant pool:
+{
   public int keep;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -433,9 +467,9 @@
       x: #x()
         android.hosttest.annotation.HostSideTestKeep
 
-  public int addOne(int);
+  public final int addOne(int);
     descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
     Code:
       stack=2, locals=2, args_size=2
          x: iload_1
@@ -505,18 +539,18 @@
             0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
             0       4     1 value   I
 
-  public static native int nativeAddThree(int);
+  public static final native int nativeAddThree(int);
     descriptor: (I)I
-    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    flags: (0x0119) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_NATIVE
     RuntimeInvisibleAnnotations:
       x: #x(#x=s#x)
         android.hosttest.annotation.HostSideTestSubstitute(
           suffix="_host"
         )
 
-  private static int nativeAddThree_host(int);
+  private static final int nativeAddThree_host(int);
     descriptor: (I)I
-    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
     Code:
       stack=2, locals=1, args_size=1
          x: iload_0
@@ -578,6 +612,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 3, attributes: 2
+Constant pool:
+{
   public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
     descriptor: Ljava/util/Set;
     flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
@@ -640,6 +676,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 6, attributes: 2
+Constant pool:
+{
   public int keep;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -764,6 +802,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 2, attributes: 2
+Constant pool:
+{
   public static boolean sInitialized;
     descriptor: Z
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -818,6 +858,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 2, attributes: 2
+Constant pool:
+{
   public static boolean sInitialized;
     descriptor: Z
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -878,6 +920,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
   super_class: #x                        // java/lang/Enum
   interfaces: 0, fields: 6, methods: 7, attributes: 3
+Constant pool:
+{
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1081,6 +1125,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
   super_class: #x                        // java/lang/Enum
   interfaces: 0, fields: 3, methods: 5, attributes: 3
+Constant pool:
+{
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1202,6 +1248,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -1256,6 +1304,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 17, attributes: 1
+Constant pool:
+{
   public int stub;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -1507,6 +1557,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 8, attributes: 5
+Constant pool:
+{
   public final java.util.function.Supplier<java.lang.Integer> mSupplier;
     descriptor: Ljava/util/function/Supplier;
     flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -1661,6 +1713,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 8, attributes: 5
+Constant pool:
+{
   public final java.util.function.Supplier<java.lang.Integer> mSupplier;
     descriptor: Ljava/util/function/Supplier;
     flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -1816,6 +1870,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 3, attributes: 3
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -1873,6 +1929,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 5, attributes: 5
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -1984,6 +2042,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 14, attributes: 2
+Constant pool:
+{
   int value;
     descriptor: I
     flags: (0x0000)
@@ -2157,6 +2217,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 7, attributes: 2
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -2263,6 +2325,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
   com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
     descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
     flags: (0x0000)
@@ -2321,6 +2385,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
   com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2();
     descriptor: ()V
     flags: (0x0000)
@@ -2375,6 +2441,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
   com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
     descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
     flags: (0x0000)
@@ -2433,6 +2501,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
   com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4();
     descriptor: ()V
     flags: (0x0000)
@@ -2487,6 +2557,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -2521,6 +2593,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -2558,6 +2632,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
   com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1();
     descriptor: ()V
     flags: (0x0000)
@@ -2613,6 +2689,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -2647,6 +2725,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 2, attributes: 3
+Constant pool:
+{
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -2694,6 +2774,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
   interfaces: 0, fields: 0, methods: 1, attributes: 3
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
     descriptor: (I)V
     flags: (0x0001) ACC_PUBLIC
@@ -2723,6 +2805,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 4, attributes: 4
+Constant pool:
+{
   public final java.util.function.Supplier<java.lang.Integer> mSupplier;
     descriptor: Ljava/util/function/Supplier;
     flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -2827,6 +2911,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -2869,6 +2955,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -2911,6 +2999,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 2, attributes: 2
+Constant pool:
+{
   private final int mValue;
     descriptor: I
     flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -2958,6 +3048,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/A
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.packagetest.A();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -2981,6 +3073,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/B
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.packagetest.B();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3004,6 +3098,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.packagetest.sub.A();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3027,6 +3123,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/B
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.packagetest.sub.B();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3050,6 +3148,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C1
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.C1();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3073,6 +3173,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C2
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C1
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.C2();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3096,6 +3198,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C3
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C2
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.C3();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3119,6 +3223,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CA
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.CA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3142,6 +3248,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CB
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.CB();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3165,6 +3273,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C1
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3188,6 +3298,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C2
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3211,6 +3323,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C3
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3234,6 +3348,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/CA
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3257,6 +3373,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/CB
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3280,6 +3398,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/CB
   interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3303,6 +3423,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3326,6 +3448,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA
   super_class: #x                         // java/lang/Object
   interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3349,6 +3473,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3372,6 +3498,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3395,6 +3523,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA
   super_class: #x                         // java/lang/Object
   interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3418,6 +3548,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3441,6 +3573,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1
   super_class: #x                         // java/lang/Object
   interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3464,6 +3598,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3
   super_class: #x                         // java/lang/Object
   interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3487,6 +3623,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3510,6 +3648,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA
   super_class: #x                         // java/lang/Object
   interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3533,6 +3673,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_None
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_None();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3556,6 +3698,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I1
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
 }
 SourceFile: "I1.java"
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
@@ -3567,6 +3711,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I2
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
 }
 SourceFile: "I2.java"
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
@@ -3578,6 +3724,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I3
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
 }
 SourceFile: "I3.java"
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
@@ -3589,6 +3737,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IA
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
 }
 SourceFile: "IA.java"
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
@@ -3600,6 +3750,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IB
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
 }
 SourceFile: "IB.java"
 ## Class: com/supported/UnsupportedClass.class
@@ -3611,6 +3763,8 @@
   this_class: #x                          // com/supported/UnsupportedClass
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 2, attributes: 2
+Constant pool:
+{
   private final int mValue;
     descriptor: I
     flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -3658,6 +3812,8 @@
   this_class: #x                         // com/unsupported/UnsupportedClass
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
   public com.unsupported.UnsupportedClass(int);
     descriptor: (I)V
     flags: (0x0001) ACC_PUBLIC
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 103e152..ad41342 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -7,6 +7,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestClassLoadHook
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
   public abstract java.lang.String value();
     descriptor: ()Ljava/lang/String;
     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -30,6 +32,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestIgnore
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestIgnore.java"
 RuntimeVisibleAnnotations:
@@ -50,6 +54,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestKeep
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestKeep.java"
 RuntimeVisibleAnnotations:
@@ -70,6 +76,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestRedirect
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestRedirect.java"
 RuntimeVisibleAnnotations:
@@ -90,6 +98,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestRedirectionClass
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
   public abstract java.lang.String value();
     descriptor: ()Ljava/lang/String;
     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -113,6 +123,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestRemove
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestRemove.java"
 RuntimeVisibleAnnotations:
@@ -133,6 +145,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestStaticInitializerKeep
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestStaticInitializerKeep.java"
 RuntimeVisibleAnnotations:
@@ -153,6 +167,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestSubstitute
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 2
+Constant pool:
+{
   public abstract java.lang.String suffix();
     descriptor: ()Ljava/lang/String;
     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
@@ -176,6 +192,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestThrow
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestThrow.java"
 RuntimeVisibleAnnotations:
@@ -196,6 +214,8 @@
   this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassKeep
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestWholeClassKeep.java"
 RuntimeVisibleAnnotations:
@@ -216,6 +236,8 @@
   this_class: #x                          // android/hosttest/annotation/tests/HostSideTestSuppress
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
+Constant pool:
+{
 }
 SourceFile: "HostSideTestSuppress.java"
 RuntimeVisibleAnnotations:
@@ -232,6 +254,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 3
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -273,6 +297,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 3
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -314,6 +340,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 0, attributes: 3
+Constant pool:
+{
 }
 SourceFile: "IPretendingAidl.java"
 NestMembers:
@@ -331,6 +359,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/R$Nested
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 2, attributes: 3
+Constant pool:
+{
   public static int[] ARRAY;
     descriptor: [I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -376,6 +406,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/R
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 3
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.R();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -396,13 +428,15 @@
   public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
   Compiled from "TinyFrameworkAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
   minor version: 0
   major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 9, attributes: 2
+Constant pool:
+{
   public int keep;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -433,9 +467,9 @@
       x: #x()
         android.hosttest.annotation.HostSideTestKeep
 
-  public int addOne(int);
+  public final int addOne(int);
     descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
     Code:
       stack=2, locals=2, args_size=2
          x: iload_1
@@ -505,18 +539,18 @@
             0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
             0       4     1 value   I
 
-  public static native int nativeAddThree(int);
+  public static final native int nativeAddThree(int);
     descriptor: (I)I
-    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    flags: (0x0119) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_NATIVE
     RuntimeInvisibleAnnotations:
       x: #x(#x=s#x)
         android.hosttest.annotation.HostSideTestSubstitute(
           suffix="_host"
         )
 
-  private static int nativeAddThree_host(int);
+  private static final int nativeAddThree_host(int);
     descriptor: (I)I
-    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
     Code:
       stack=2, locals=1, args_size=1
          x: iload_0
@@ -578,6 +612,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 3, attributes: 2
+Constant pool:
+{
   public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
     descriptor: Ljava/util/Set;
     flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
@@ -640,6 +676,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 6, attributes: 2
+Constant pool:
+{
   public int keep;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -764,6 +802,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 2, attributes: 2
+Constant pool:
+{
   public static boolean sInitialized;
     descriptor: Z
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -818,6 +858,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 2, attributes: 2
+Constant pool:
+{
   public static boolean sInitialized;
     descriptor: Z
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -878,6 +920,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
   super_class: #x                        // java/lang/Enum
   interfaces: 0, fields: 6, methods: 7, attributes: 3
+Constant pool:
+{
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1081,6 +1125,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
   super_class: #x                        // java/lang/Enum
   interfaces: 0, fields: 3, methods: 5, attributes: 3
+Constant pool:
+{
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
@@ -1202,6 +1248,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -1256,6 +1304,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 17, attributes: 1
+Constant pool:
+{
   public int stub;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -1507,6 +1557,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 8, attributes: 5
+Constant pool:
+{
   public final java.util.function.Supplier<java.lang.Integer> mSupplier;
     descriptor: Ljava/util/function/Supplier;
     flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -1661,6 +1713,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 8, attributes: 5
+Constant pool:
+{
   public final java.util.function.Supplier<java.lang.Integer> mSupplier;
     descriptor: Ljava/util/function/Supplier;
     flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -1816,6 +1870,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 3, attributes: 3
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -1873,6 +1929,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 5, attributes: 5
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -1984,6 +2042,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 14, attributes: 2
+Constant pool:
+{
   int value;
     descriptor: I
     flags: (0x0000)
@@ -2157,6 +2217,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 7, attributes: 2
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -2263,6 +2325,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 1, methods: 3, attributes: 5
+Constant pool:
+{
   final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
     flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
@@ -2328,6 +2392,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
   com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2();
     descriptor: ()V
     flags: (0x0000)
@@ -2382,6 +2448,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 1, methods: 3, attributes: 5
+Constant pool:
+{
   final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
     flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
@@ -2447,6 +2515,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
   com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4();
     descriptor: ()V
     flags: (0x0000)
@@ -2501,6 +2571,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -2535,6 +2607,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 1, attributes: 3
+Constant pool:
+{
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -2579,6 +2653,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 3, attributes: 5
+Constant pool:
+{
   com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1();
     descriptor: ()V
     flags: (0x0000)
@@ -2634,6 +2710,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -2668,6 +2746,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 2, attributes: 3
+Constant pool:
+{
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -2715,6 +2795,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
   interfaces: 0, fields: 0, methods: 1, attributes: 3
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
     descriptor: (I)V
     flags: (0x0001) ACC_PUBLIC
@@ -2744,6 +2826,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 2, methods: 4, attributes: 4
+Constant pool:
+{
   public final java.util.function.Supplier<java.lang.Integer> mSupplier;
     descriptor: Ljava/util/function/Supplier;
     flags: (0x0011) ACC_PUBLIC, ACC_FINAL
@@ -2848,6 +2932,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -2890,6 +2976,8 @@
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -2932,6 +3020,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 2, attributes: 2
+Constant pool:
+{
   private final int mValue;
     descriptor: I
     flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -2979,6 +3069,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/A
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.packagetest.A();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3002,6 +3094,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/B
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.packagetest.B();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3025,6 +3119,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.packagetest.sub.A();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3048,6 +3144,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/B
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.packagetest.sub.B();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3071,6 +3169,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C1
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.C1();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3094,6 +3194,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C2
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C1
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.C2();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3117,6 +3219,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C3
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C2
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.C3();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3140,6 +3244,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CA
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.CA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3163,6 +3269,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CB
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.CB();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3186,6 +3294,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C1
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3209,6 +3319,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C2
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3232,6 +3344,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C3
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3255,6 +3369,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/CA
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3278,6 +3394,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/CB
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3301,6 +3419,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA
   super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/CB
   interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3324,6 +3444,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3347,6 +3469,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA
   super_class: #x                         // java/lang/Object
   interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3370,6 +3494,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3393,6 +3519,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3416,6 +3544,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA
   super_class: #x                         // java/lang/Object
   interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3439,6 +3569,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3462,6 +3594,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1
   super_class: #x                         // java/lang/Object
   interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3485,6 +3619,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3
   super_class: #x                         // java/lang/Object
   interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3508,6 +3644,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3531,6 +3669,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA
   super_class: #x                         // java/lang/Object
   interfaces: 2, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3554,6 +3694,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_None
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 1, attributes: 1
+Constant pool:
+{
   public com.android.hoststubgen.test.tinyframework.subclasstest.Class_None();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
@@ -3577,6 +3719,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I1
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
 }
 SourceFile: "I1.java"
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
@@ -3588,6 +3732,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I2
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
 }
 SourceFile: "I2.java"
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
@@ -3599,6 +3745,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I3
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
 }
 SourceFile: "I3.java"
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
@@ -3610,6 +3758,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IA
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
 }
 SourceFile: "IA.java"
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
@@ -3621,6 +3771,8 @@
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IB
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 0, attributes: 1
+Constant pool:
+{
 }
 SourceFile: "IB.java"
 ## Class: com/supported/UnsupportedClass.class
@@ -3632,6 +3784,8 @@
   this_class: #x                          // com/supported/UnsupportedClass
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 1, methods: 2, attributes: 2
+Constant pool:
+{
   private final int mValue;
     descriptor: I
     flags: (0x0012) ACC_PRIVATE, ACC_FINAL
@@ -3679,6 +3833,8 @@
   this_class: #x                         // com/unsupported/UnsupportedClass
   super_class: #x                         // java/lang/Object
   interfaces: 0, fields: 0, methods: 2, attributes: 2
+Constant pool:
+{
   public com.unsupported.UnsupportedClass(int);
     descriptor: (I)V
     flags: (0x0001) ACC_PUBLIC
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
index 3415deb..674937d 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
@@ -28,7 +28,7 @@
 @HostSideTestKeep
 @HostSideTestClassLoadHook(
         "com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded")
-public class TinyFrameworkAnnotations {
+public final class TinyFrameworkAnnotations {
     @HostSideTestKeep
     public TinyFrameworkAnnotations() {
     }
@@ -42,7 +42,7 @@
     public int remove;
 
     @HostSideTestKeep
-    public int addOne(int value) {
+    public final int addOne(int value) {
         return value + 1;
     }
 
@@ -61,10 +61,10 @@
     }
 
     @HostSideTestSubstitute(suffix = "_host")
-    public static native int nativeAddThree(int value);
+    public final static native int nativeAddThree(int value);
 
     // This method is private, but at runtime, it'll inherit the visibility of the original method
-    private static int nativeAddThree_host(int value) {
+    private final static int nativeAddThree_host(int value) {
         return value + 3;
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
index b94fa2f..8b758d2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
@@ -39,6 +39,8 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.annotation.VisibleForTesting;
+
 /**
  * Implements "Automatically click on mouse stop" feature.
  *
@@ -69,10 +71,10 @@
     private final int mUserId;
 
     // Lazily created on the first mouse motion event.
-    private ClickScheduler mClickScheduler;
-    private AutoclickSettingsObserver mAutoclickSettingsObserver;
-    private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
-    private AutoclickIndicatorView mAutoclickIndicatorView;
+    @VisibleForTesting ClickScheduler mClickScheduler;
+    @VisibleForTesting AutoclickSettingsObserver mAutoclickSettingsObserver;
+    @VisibleForTesting AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
+    @VisibleForTesting AutoclickIndicatorView mAutoclickIndicatorView;
     private WindowManager mWindowManager;
 
     public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) {
@@ -360,7 +362,8 @@
      * moving. The click is first scheduled when a mouse movement is detected, and then further
      * delayed on every sufficient mouse movement.
      */
-    final private class ClickScheduler implements Runnable {
+    @VisibleForTesting
+    final class ClickScheduler implements Runnable {
         /**
          * Minimal distance pointer has to move relative to anchor in order for movement not to be
          * discarded as noise. Anchor is the position of the last MOVE event that was not considered
@@ -474,6 +477,11 @@
             }
         }
 
+        @VisibleForTesting
+        int getDelayForTesting() {
+            return mDelay;
+        }
+
         /**
          * Updates the time at which click sequence should occur.
          *
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
index bf50151..f87dcdb2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
@@ -28,6 +28,8 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.LinearInterpolator;
 
+import androidx.annotation.VisibleForTesting;
+
 // A visual indicator for the autoclick feature.
 public class AutoclickIndicatorView extends View {
     private static final String TAG = AutoclickIndicatorView.class.getSimpleName();
@@ -37,7 +39,7 @@
 
     static final int MINIMAL_ANIMATION_DURATION = 50;
 
-    private float mRadius = AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
+    private int mRadius = AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
 
     private final Paint mPaint;
 
@@ -112,6 +114,11 @@
         mRadius = radius;
     }
 
+    @VisibleForTesting
+    int getRadiusForTesting() {
+        return mRadius;
+    }
+
     public void redrawIndicator() {
         showIndicator = true;
         invalidate();
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 0cbbf6d..2c106d3 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -16,8 +16,6 @@
 
 package com.android.server.accessibility.gestures;
 
-import static android.accessibilityservice.AccessibilityTrace.FLAGS_GESTURE;
-import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_HOVER_ENTER;
@@ -86,8 +84,6 @@
 public class TouchExplorer extends BaseEventStreamTransformation
         implements GestureManifold.Listener {
 
-    private static final long LOGGING_FLAGS = FLAGS_GESTURE | FLAGS_INPUT_FILTER;
-
     // Tag for logging received events.
     private static final String LOG_TAG = "TouchExplorer";
 
@@ -261,10 +257,6 @@
 
     @Override
     public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
-            mAms.getTraceManager().logTrace(LOG_TAG + ".onMotionEvent", LOGGING_FLAGS,
-                    "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
-        }
         if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
             super.onMotionEvent(event, rawEvent, policyFlags);
             return;
@@ -323,9 +315,8 @@
 
     @Override
     public void onAccessibilityEvent(AccessibilityEvent event) {
-        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
-            mAms.getTraceManager().logTrace(LOG_TAG + ".onAccessibilityEvent",
-                    LOGGING_FLAGS, "event=" + event);
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Received A11y Event. event=" + event);
         }
         final int eventType = event.getEventType();
 
@@ -383,9 +374,9 @@
 
     @Override
     public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
-            mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTapAndHold", LOGGING_FLAGS,
-                    "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "Double tap and hold. event="
+                    + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
         }
         if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) {
             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
@@ -403,9 +394,9 @@
 
     @Override
     public boolean onDoubleTap(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
-            mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTap", LOGGING_FLAGS,
-                    "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "Double tap. event="
+                    + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
         }
         mAms.onTouchInteractionEnd();
         // Remove pending event deliveries.
@@ -463,8 +454,8 @@
 
     @Override
     public boolean onGestureStarted() {
-        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
-            mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureStarted", LOGGING_FLAGS);
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "Gesture started.");
         }
         // We have to perform gesture detection, so
         // clear the current state and try to detect.
@@ -479,9 +470,8 @@
 
     @Override
     public boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent) {
-        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
-            mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCompleted",
-                    LOGGING_FLAGS, "event=" + gestureEvent);
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "Gesture completed. gestureEvent=" + gestureEvent);
         }
         endGestureDetection(true);
         mSendTouchInteractionEndDelayed.cancel();
@@ -491,10 +481,11 @@
 
     @Override
     public boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
-            mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCancelled", LOGGING_FLAGS,
-                    "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "Gesture cancelled. event="
+                    + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
         }
+
         if (mState.isGestureDetecting()) {
             endGestureDetection(event.getActionMasked() == ACTION_UP);
             return true;
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index cffdfbd..8ef44ad 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -5666,6 +5666,16 @@
         }
 
         public boolean canAccessAppWidget(Widget widget, int uid, String packageName) {
+            if (packageName != null
+                    && widget.provider != null
+                    && isDifferentPackageFromProvider(widget.provider, packageName)
+                    && widget.host != null
+                    && isDifferentPackageFromHost(widget.host, packageName)) {
+                // An AppWidget can only be accessed by either
+                // 1. The package that provides the AppWidget.
+                // 2. The package that hosts the AppWidget.
+                return false;
+            }
             if (isHostInPackageForUid(widget.host, uid, packageName)) {
                 // Apps hosting the AppWidget have access to it.
                 return true;
@@ -5768,6 +5778,22 @@
                     && provider.id.componentName.getPackageName().equals(packageName);
         }
 
+        private boolean isDifferentPackageFromHost(
+                @NonNull final Host host, @NonNull final String packageName) {
+            if (host.id == null || host.id.packageName == null) {
+                return true;
+            }
+            return !packageName.equals(host.id.packageName);
+        }
+
+        private boolean isDifferentPackageFromProvider(
+                @NonNull final Provider provider, @NonNull final String packageName) {
+            if (provider.id == null || provider.id.componentName == null) {
+                return true;
+            }
+            return !packageName.equals(provider.id.componentName.getPackageName());
+        }
+
         private boolean isProfileEnabled(int profileId) {
             final long identity = Binder.clearCallingIdentity();
             try {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0e2e505..a37b2b9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -612,7 +612,7 @@
 
         @Override
         public void enablePermissionsSync(int associationId) {
-            if (getCallingUid() != SYSTEM_UID) {
+            if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
                 throw new SecurityException("Caller must be system UID");
             }
             mSystemDataTransferProcessor.enablePermissionsSync(associationId);
@@ -620,7 +620,7 @@
 
         @Override
         public void disablePermissionsSync(int associationId) {
-            if (getCallingUid() != SYSTEM_UID) {
+            if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
                 throw new SecurityException("Caller must be system UID");
             }
             mSystemDataTransferProcessor.disablePermissionsSync(associationId);
@@ -628,7 +628,7 @@
 
         @Override
         public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
-            if (getCallingUid() != SYSTEM_UID) {
+            if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
                 throw new SecurityException("Caller must be system UID");
             }
             return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
@@ -704,7 +704,7 @@
 
         @Override
         public byte[] getBackupPayload(int userId) {
-            if (getCallingUid() != SYSTEM_UID) {
+            if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
                 throw new SecurityException("Caller must be system");
             }
             return mBackupRestoreProcessor.getBackupPayload(userId);
@@ -712,7 +712,7 @@
 
         @Override
         public void applyRestoredPayload(byte[] payload, int userId) {
-            if (getCallingUid() != SYSTEM_UID) {
+            if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) {
                 throw new SecurityException("Caller must be system");
             }
             mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 2804945..3508f2f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -106,7 +106,7 @@
                     boolean selfManaged = getNextBooleanArg();
                     final MacAddress macAddress = MacAddress.fromString(address);
                     mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
-                            deviceProfile, deviceProfile, /* associatedDevice */ null, false,
+                            deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
                             /* callback */ null, /* resultReceiver */ null, /* deviceIcon */ null);
                 }
                 break;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index f401e6b..c385fba 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -490,7 +490,7 @@
                         ? mParams.getBlockedActivities()
                         : mParams.getAllowedActivities());
 
-        if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) {
+        if (mParams.getInputMethodComponent() != null) {
             final String imeId = mParams.getInputMethodComponent().flattenToShortString();
             Slog.d(TAG, "Setting custom input method " + imeId + " as default for virtual device "
                     + deviceId);
@@ -807,7 +807,7 @@
             }
 
             // Clear any previously set custom IME components.
-            if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) {
+            if (mParams.getInputMethodComponent() != null) {
                 InputMethodManagerInternal.get().setVirtualDeviceInputMethodForAllUsers(
                         mDeviceId, null);
             }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 0b335d3..4dd8b14 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -820,13 +820,12 @@
 
         @Override
         public void onAuthenticationPrompt(int uid) {
-            synchronized (mVirtualDeviceManagerLock) {
-                for (int i = 0; i < mVirtualDevices.size(); i++) {
-                    VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
-                    device.showToastWhereUidIsRunning(uid,
-                            R.string.app_streaming_blocked_message_for_fingerprint_dialog,
-                            Toast.LENGTH_LONG, Looper.getMainLooper());
-                }
+            ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+            for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+                VirtualDeviceImpl device = virtualDevicesSnapshot.get(i);
+                device.showToastWhereUidIsRunning(uid,
+                        R.string.app_streaming_blocked_message_for_fingerprint_dialog,
+                        Toast.LENGTH_LONG, Looper.getMainLooper());
             }
         }
 
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index abfb826..df47c98 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -98,6 +98,8 @@
     private static final int MSG_INVALIDATE_TOKEN = 1;
     private static final int MAX_TOKEN_VALID_DURATION_MS = 1_000 * 60 * 10; // 10 minutes
 
+    private static final boolean DEBUG = false;
+
     private final Context mContext;
     private final ActivityTaskManagerInternal mAtmInternal;
     private final PackageManagerInternal mPackageManager;
@@ -121,6 +123,7 @@
                         final Bundle data,
                         final int activityIndex,
                         final int activityCount) {
+
                     final IContextualSearchCallback callback;
                     synchronized (mLock) {
                         callback = mStateCallback;
@@ -160,7 +163,7 @@
 
     public ContextualSearchManagerService(@NonNull Context context) {
         super(context);
-        if (DEBUG_USER) Log.d(TAG, "ContextualSearchManagerService created");
+        if (DEBUG) Log.d(TAG, "ContextualSearchManagerService created");
         mContext = context;
         mAtmInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityTaskManagerInternal.class));
@@ -206,7 +209,7 @@
                 mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_PACKAGE);
                 mTemporaryHandler = null;
             }
-            if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage reset.");
+            if (DEBUG) Log.d(TAG, "mTemporaryPackage reset.");
             mTemporaryPackage = null;
             updateSecureSetting();
         }
@@ -239,7 +242,7 @@
             mTemporaryPackage = temporaryPackage;
             updateSecureSetting();
             mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_PACKAGE, durationMs);
-            if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage);
+            if (DEBUG) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage);
         }
     }
 
@@ -256,7 +259,7 @@
                                 + durationMs + ")");
             }
             mTokenValidDurationMs = durationMs;
-            if (DEBUG_USER) Log.d(TAG, "mTokenValidDurationMs set to " + durationMs);
+            if (DEBUG) Log.d(TAG, "mTokenValidDurationMs set to " + durationMs);
         }
     }
 
@@ -268,12 +271,12 @@
 
     private Intent getResolvedLaunchIntent(int userId) {
         synchronized (this) {
-            if(DEBUG_USER) Log.d(TAG, "Attempting to getResolvedLaunchIntent");
+            if(DEBUG) Log.d(TAG, "Attempting to getResolvedLaunchIntent");
             // If mTemporaryPackage is not null, use it to get the ContextualSearch intent.
             String csPkgName = getContextualSearchPackageName();
             if (csPkgName.isEmpty()) {
                 // Return null if csPackageName is not specified.
-                if (DEBUG_USER) Log.w(TAG, "getContextualSearchPackageName is empty");
+                if (DEBUG) Log.w(TAG, "getContextualSearchPackageName is empty");
                 return null;
             }
             Intent launchIntent = new Intent(
@@ -282,12 +285,12 @@
             ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(
                     launchIntent, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
             if (resolveInfo == null) {
-                if (DEBUG_USER) Log.w(TAG, "resolveInfo is null");
+                if (DEBUG) Log.w(TAG, "resolveInfo is null");
                 return null;
             }
             ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
             if (componentName == null) {
-                if (DEBUG_USER) Log.w(TAG, "componentName is null");
+                if (DEBUG) Log.w(TAG, "componentName is null");
                 return null;
             }
             launchIntent.setComponent(componentName);
@@ -298,11 +301,11 @@
     private Intent getContextualSearchIntent(int entrypoint, int userId, CallbackToken mToken) {
         final Intent launchIntent = getResolvedLaunchIntent(userId);
         if (launchIntent == null) {
-            if (DEBUG_USER) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null");
+            if (DEBUG) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null");
             return null;
         }
 
-        if (DEBUG_USER) Log.d(TAG, "Launch component: " + launchIntent.getComponent());
+        if (DEBUG) Log.d(TAG, "Launch component: " + launchIntent.getComponent());
         launchIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION
                 | FLAG_ACTIVITY_NO_USER_ACTION | FLAG_ACTIVITY_CLEAR_TASK);
         launchIntent.putExtra(
@@ -355,7 +358,7 @@
                     TYPE_NAVIGATION_BAR_PANEL,
                     TYPE_POINTER));
         } else {
-            if (DEBUG_USER) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null");
+            if (DEBUG) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null");
             shb = null;
         }
         final Bitmap bm = shb != null ? shb.asBitmap() : null;
@@ -429,7 +432,7 @@
                     mTokenHandler.removeMessages(MSG_INVALIDATE_TOKEN);
                     mTokenHandler = null;
                 }
-                if (DEBUG_USER) Log.d(TAG, "mToken invalidated.");
+                if (DEBUG) Log.d(TAG, "mToken invalidated.");
                 mToken = null;
             }
         }
@@ -459,7 +462,7 @@
         @Override
         public void startContextualSearch(int entrypoint) {
             synchronized (this) {
-                if (DEBUG_USER) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint);
+                if (DEBUG) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint);
                 enforcePermission("startContextualSearch");
                 final int callingUserId = Binder.getCallingUserHandle().getIdentifier();
 
@@ -474,7 +477,7 @@
                         getContextualSearchIntent(entrypoint, callingUserId, mToken);
                     if (launchIntent != null) {
                         int result = invokeContextualSearchIntent(launchIntent, callingUserId);
-                        if (DEBUG_USER) Log.d(TAG, "Launch result: " + result);
+                        if (DEBUG) Log.d(TAG, "Launch result: " + result);
                     }
                 });
             }
@@ -484,11 +487,11 @@
         public void getContextualSearchState(
                 @NonNull IBinder token,
                 @NonNull IContextualSearchCallback callback) {
-            if (DEBUG_USER) {
+            if (DEBUG) {
                 Log.i(TAG, "getContextualSearchState token: " + token + ", callback: " + callback);
             }
             if (mToken == null || !mToken.getToken().equals(token)) {
-                if (DEBUG_USER) {
+                if (DEBUG) {
                     Log.e(TAG, "getContextualSearchState: invalid token, returning error");
                 }
                 try {
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 1588e04..7a5b866 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -40,7 +40,9 @@
 import android.util.EventLog;
 import android.util.Slog;
 import android.util.Xml;
+import android.util.proto.ProtoFieldFilter;
 import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoParseException;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -49,10 +51,13 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.am.DropboxRateLimiter;
+import com.android.server.os.TombstoneProtos.Tombstone;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
@@ -64,6 +69,7 @@
 import java.nio.file.attribute.PosixFilePermissions;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -392,6 +398,129 @@
         writeTimestamps(timestamps);
     }
 
+    /**
+     * Processes a tombstone file and adds it to the DropBox after filtering and applying
+     * rate limiting.
+     * Filtering removes memory sections from the tombstone proto to reduce size while preserving
+     * critical information. The filtered tombstone is then added to DropBox in both proto
+     * and text formats, with the text format derived from the filtered proto.
+     * Rate limiting is applied as it is the case with other crash types.
+     *
+     * @param ctx Context
+     * @param tombstone path to the tombstone
+     * @param processName the name of the process corresponding to the tombstone
+     * @param tmpFileLock the lock for reading/writing tmp files
+     */
+    public static void filterAndAddTombstoneToDropBox(
+                Context ctx, File tombstone, String processName, ReentrantLock tmpFileLock) {
+        final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
+        if (db == null) {
+            Slog.e(TAG, "Can't log tombstone: DropBoxManager not available");
+            return;
+        }
+        File filteredProto = null;
+        // Check if we should rate limit and abort early if needed.
+        DropboxRateLimiter.RateLimitResult rateLimitResult =
+                sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
+        if (rateLimitResult.shouldRateLimit()) return;
+
+        HashMap<String, Long> timestamps = readTimestamps();
+        try {
+            tmpFileLock.lock();
+            Slog.i(TAG, "Filtering tombstone file: " + tombstone.getName());
+            // Create a temporary tombstone without memory sections.
+            filteredProto = createTempTombstoneWithoutMemory(tombstone);
+            Slog.i(TAG, "Generated tombstone file: " + filteredProto.getName());
+
+            if (recordFileTimestamp(tombstone, timestamps)) {
+                // We need to attach the count indicating the number of dropped dropbox entries
+                // due to rate limiting. Do this by enclosing the proto tombsstone in a
+                // container proto that has the dropped entry count and the proto tombstone as
+                // bytes (to avoid the complexity of reading and writing nested protos).
+                Slog.i(TAG, "Adding tombstone " + filteredProto.getName() + " to dropbox");
+                addAugmentedProtoToDropbox(filteredProto, db, rateLimitResult);
+            }
+            // Always add the text version of the tombstone to the DropBox, in order to
+            // match the previous behaviour.
+            Slog.i(TAG, "Adding text tombstone version of " + filteredProto.getName()
+                    + " to dropbox");
+            addTextTombstoneFromProtoToDropbox(filteredProto, db, timestamps, rateLimitResult);
+
+        } catch (IOException | ProtoParseException e) {
+            Slog.e(TAG, "Failed to log tombstone '" + tombstone.getName()
+                    + "' to DropBox. Error during processing or writing: " + e.getMessage(), e);
+        }  finally {
+            if (filteredProto != null) {
+                filteredProto.delete();
+            }
+            tmpFileLock.unlock();
+        }
+        writeTimestamps(timestamps);
+    }
+
+    /**
+     * Creates a temporary tombstone file by filtering out memory mapping fields.
+     * This ensures that the unneeded memory mapping data is removed from the tombstone
+     * before adding it to Dropbox
+     *
+     * @param tombstone the original tombstone file to process
+     * @return a temporary file containing the filtered tombstone data
+     * @throws IOException if an I/O error occurs during processing
+     */
+    private static File createTempTombstoneWithoutMemory(File tombstone) throws IOException {
+        // Process the proto tombstone file and write it to a temporary file
+        File tombstoneProto =
+                File.createTempFile(tombstone.getName(), ".pb.tmp", TOMBSTONE_TMP_DIR);
+        ProtoFieldFilter protoFilter =
+                new ProtoFieldFilter(fieldNumber -> fieldNumber != (int) Tombstone.MEMORY_MAPPINGS);
+
+        try (FileInputStream fis = new FileInputStream(tombstone);
+                BufferedInputStream bis = new BufferedInputStream(fis);
+                FileOutputStream fos = new FileOutputStream(tombstoneProto);
+                BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+            protoFilter.filter(bis, bos);
+            return tombstoneProto;
+        }
+    }
+
+    private static void addTextTombstoneFromProtoToDropbox(File tombstone, DropBoxManager db,
+            HashMap<String, Long> timestamps, DropboxRateLimiter.RateLimitResult rateLimitResult) {
+        File tombstoneTextFile = null;
+
+        try {
+            tombstoneTextFile = File.createTempFile(tombstone.getName(),
+                ".pb.txt.tmp", TOMBSTONE_TMP_DIR);
+
+            // Create a ProcessBuilder to execute pbtombstone
+            ProcessBuilder pb = new ProcessBuilder("/system/bin/pbtombstone", tombstone.getPath());
+            pb.redirectOutput(tombstoneTextFile);
+            Process process = pb.start();
+
+            // Wait 10 seconds for the process to complete
+            if (!process.waitFor(10, TimeUnit.SECONDS)) {
+                Slog.e(TAG, "pbtombstone timed out");
+                process.destroyForcibly();
+                return;
+            }
+
+            int exitCode = process.exitValue();
+            if (exitCode != 0) {
+                Slog.e(TAG, "pbtombstone failed with exit code " + exitCode);
+            } else {
+                final String headers = getBootHeadersToLogAndUpdate()
+                        + rateLimitResult.createHeader();
+                addFileToDropBox(db, timestamps, headers, tombstoneTextFile.getPath(), LOG_SIZE,
+                        TAG_TOMBSTONE);
+            }
+        } catch (IOException | InterruptedException e) {
+            Slog.e(TAG, "Failed to process tombstone with pbtombstone", e);
+        } finally {
+            if (tombstoneTextFile != null) {
+                tombstoneTextFile.delete();
+            }
+        }
+    }
+
     private static void addAugmentedProtoToDropbox(
                 File tombstone, DropBoxManager db,
                 DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 2216f27..f7eaa15 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2553,13 +2553,12 @@
                 final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
 
                 // We can't isolate app data and storage data as parent zygote already did that.
-                startResult = appZygote.getProcess().start(entryPoint,
-                        app.processName, uid, uid, gids, runtimeFlags, mountExternal,
+                startResult = appZygote.startProcess(entryPoint,
+                        app.processName, uid, gids, runtimeFlags, mountExternal,
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
-                        app.info.dataDir, null, app.info.packageName,
-                        /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
-                        app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
-                        false, false, false,
+                        app.info.dataDir, app.info.packageName, isTopApp,
+                        app.getDisabledCompatChanges(), pkgDataInfoMap,
+                        allowlistedAppDataInfoMap,
                         new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
             } else {
                 regularZygote = true;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d3a5254..a54a663 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -16,12 +16,23 @@
 
 package com.android.server.am;
 
+import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
+import static com.android.aconfig_new_storage.Flags.enableAconfigdFromMainline;
+import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
+import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+
+import android.aconfigd.Aconfigd.StorageRequestMessage;
+import android.aconfigd.Aconfigd.StorageRequestMessages;
+import android.aconfigd.Aconfigd.StorageReturnMessage;
+import android.aconfigd.Aconfigd.StorageReturnMessages;
 import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.database.ContentObserver;
-import android.net.Uri;
-import android.net.LocalSocketAddress;
 import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.SystemProperties;
@@ -35,28 +46,13 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.providers.settings.Flags;
 
-import android.aconfigd.Aconfigd.StorageRequestMessage;
-import android.aconfigd.Aconfigd.StorageRequestMessages;
-import android.aconfigd.Aconfigd.StorageReturnMessage;
-import android.aconfigd.Aconfigd.StorageReturnMessages;
-import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
-import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
-import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
-import static com.android.aconfig_new_storage.Flags.enableAconfigdFromMainline;
-
+import java.io.BufferedReader;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
 import java.util.HashSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Set;
-import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 
 /**
  * Maps system settings to system properties.
@@ -271,6 +267,7 @@
         "wear_sysui",
         "wear_system_managed_surfaces",
         "wear_watchfaces",
+        "web_apps_on_chromeos_and_android",
         "window_surfaces",
         "windowing_frontend",
         "xr",
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index f58e3f8..23edafc 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -78,6 +78,7 @@
      * This is for verifying the UID report flow.
      */
     private static final boolean VALIDATE_UID_STATES = true;
+    @GuardedBy("mLock")
     private final ActiveUids mValidateUids;
 
     UidObserverController(@NonNull Handler handler) {
@@ -285,31 +286,30 @@
         }
         mUidObservers.finishBroadcast();
 
-        if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
-            for (int j = 0; j < numUidChanges; ++j) {
-                final ChangeRecord item = mActiveUidChanges[j];
-                if ((item.change & UidRecord.CHANGE_GONE) != 0) {
-                    mValidateUids.remove(item.uid);
-                } else {
-                    UidRecord validateUid = mValidateUids.get(item.uid);
-                    if (validateUid == null) {
-                        validateUid = new UidRecord(item.uid, null);
-                        mValidateUids.put(item.uid, validateUid);
+        synchronized (mLock) {
+            if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
+                for (int j = 0; j < numUidChanges; ++j) {
+                    final ChangeRecord item = mActiveUidChanges[j];
+                    if ((item.change & UidRecord.CHANGE_GONE) != 0) {
+                        mValidateUids.remove(item.uid);
+                    } else {
+                        UidRecord validateUid = mValidateUids.get(item.uid);
+                        if (validateUid == null) {
+                            validateUid = new UidRecord(item.uid, null);
+                            mValidateUids.put(item.uid, validateUid);
+                        }
+                        if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
+                            validateUid.setIdle(true);
+                        } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
+                            validateUid.setIdle(false);
+                        }
+                        validateUid.setSetProcState(item.procState);
+                        validateUid.setCurProcState(item.procState);
+                        validateUid.setSetCapability(item.capability);
+                        validateUid.setCurCapability(item.capability);
                     }
-                    if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
-                        validateUid.setIdle(true);
-                    } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
-                        validateUid.setIdle(false);
-                    }
-                    validateUid.setSetProcState(item.procState);
-                    validateUid.setCurProcState(item.procState);
-                    validateUid.setSetCapability(item.capability);
-                    validateUid.setCurCapability(item.capability);
                 }
             }
-        }
-
-        synchronized (mLock) {
             for (int j = 0; j < numUidChanges; j++) {
                 final ChangeRecord changeRecord = mActiveUidChanges[j];
                 changeRecord.isPending = false;
@@ -436,7 +436,9 @@
     }
 
     UidRecord getValidateUidRecord(int uid) {
-        return mValidateUids.get(uid);
+        synchronized (mLock) {
+            return mValidateUids.get(uid);
+        }
     }
 
     void dump(@NonNull PrintWriter pw, @Nullable String dumpPackage) {
@@ -491,12 +493,16 @@
 
     boolean dumpValidateUids(@NonNull PrintWriter pw, @Nullable String dumpPackage, int dumpAppId,
             @NonNull String header, boolean needSep) {
-        return mValidateUids.dump(pw, dumpPackage, dumpAppId, header, needSep);
+        synchronized (mLock) {
+            return mValidateUids.dump(pw, dumpPackage, dumpAppId, header, needSep);
+        }
     }
 
     void dumpValidateUidsProto(@NonNull ProtoOutputStream proto, @Nullable String dumpPackage,
             int dumpAppId, long fieldId) {
-        mValidateUids.dumpProto(proto, dumpPackage, dumpAppId, fieldId);
+        synchronized (mLock) {
+            mValidateUids.dumpProto(proto, dumpPackage, dumpAppId, fieldId);
+        }
     }
 
     static final class ChangeRecord {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index d2073aa..8e09e3b 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -33,6 +33,7 @@
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
@@ -3267,6 +3268,11 @@
                         packageName);
             }
             if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+                // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+                if (code == OP_BLUETOOTH_CONNECT) {
+                    Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as incoming "
+                            + "package: " + packageName + " and uid: " + uid + " is invalid");
+                }
                 return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                         packageName);
             }
@@ -3306,6 +3312,13 @@
             }
         } catch (SecurityException e) {
             logVerifyAndGetBypassFailure(uid, e, "noteOperation");
+            // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+            if (code == OP_BLUETOOTH_CONNECT) {
+                Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+                        + " verifyAndGetBypass returned a SecurityException for package: "
+                        + packageName + " and uid: " + uid + " and attributionTag: "
+                        + attributionTag, e);
+            }
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
@@ -3323,6 +3336,17 @@
                 if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
                         + " package " + packageName + "flags: " +
                         AppOpsManager.flagsToString(flags));
+                // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+                if (code == OP_BLUETOOTH_CONNECT) {
+                    Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+                            + " #getOpsLocked returned null for"
+                            + " uid: " + uid
+                            + " packageName: " + packageName
+                            + " attributionTag: " + attributionTag
+                            + " pvr.isAttributionTagValid: " + pvr.isAttributionTagValid
+                            + " pvr.bypass: " + pvr.bypass);
+                    Slog.e(TAG, "mUidStates.get(" + uid + "): " + mUidStates.get(uid));
+                }
                 return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                         packageName);
             }
@@ -3367,6 +3391,11 @@
                     attributedOp.rejected(uidState.getState(), flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
                             virtualDeviceId, flags, uidMode);
+                    // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+                    if (code == OP_BLUETOOTH_CONNECT && uidMode == MODE_ERRORED) {
+                        Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+                                + " uid mode is MODE_ERRORED");
+                    }
                     return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
                 }
             } else {
@@ -3386,6 +3415,11 @@
                     attributedOp.rejected(uidState.getState(), flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
                             virtualDeviceId, flags, mode);
+                    // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+                    if (code == OP_BLUETOOTH_CONNECT && mode == MODE_ERRORED) {
+                        Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+                                + " package mode is MODE_ERRORED");
+                    }
                     return new SyncNotedAppOp(mode, code, attributionTag, packageName);
                 }
             }
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
index 695032e..86f5d9b 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
@@ -27,9 +27,13 @@
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteRawStatement;
 import android.os.Environment;
+import android.os.SystemClock;
+import android.permission.flags.Flags;
 import android.util.IntArray;
 import android.util.Slog;
 
+import com.android.internal.util.FrameworkStatsLog;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
@@ -76,6 +80,10 @@
         if (opEvents.isEmpty()) {
             return;
         }
+        long startTime = 0;
+        if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+            startTime = SystemClock.elapsedRealtime();
+        }
 
         SQLiteDatabase db = getWritableDatabase();
         // TODO (b/383157289) what if database is busy and can't start a transaction? will read
@@ -117,6 +125,11 @@
                         + " file size (bytes) : " + getDatabaseFile().length(), exception);
             }
         }
+        if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+            long timeTaken = SystemClock.elapsedRealtime() - startTime;
+            FrameworkStatsLog.write(FrameworkStatsLog.SQLITE_DISCRETE_OP_EVENT_REPORTED,
+                    -1, timeTaken, getDatabaseFile().length());
+        }
     }
 
     private void bindTextOrNull(SQLiteRawStatement statement, int index, @Nullable String text) {
@@ -181,7 +194,10 @@
                 uidFilter, packageNameFilter,
                 attributionTagFilter, opCodesFilter, opFlagsFilter);
         String sql = buildSql(conditions, orderByColumn, limit);
-
+        long startTime = 0;
+        if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+            startTime = SystemClock.elapsedRealtime();
+        }
         SQLiteDatabase db = getReadableDatabase();
         List<DiscreteOpsSqlRegistry.DiscreteOp> results = new ArrayList<>();
         db.beginTransactionReadOnly();
@@ -225,6 +241,11 @@
         } finally {
             db.endTransaction();
         }
+        if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+            long timeTaken = SystemClock.elapsedRealtime() - startTime;
+            FrameworkStatsLog.write(FrameworkStatsLog.SQLITE_DISCRETE_OP_EVENT_REPORTED,
+                    timeTaken, -1, getDatabaseFile().length());
+        }
         return results;
     }
 
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index ba391d0..5aa2a6b 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -205,18 +205,8 @@
         mContext = context;
         if (Flags.enableSqliteAppopsAccesses()) {
             mDiscreteRegistry = new DiscreteOpsSqlRegistry(context);
-            if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) {
-                DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry;
-                DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(context);
-                DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry);
-            }
         } else {
             mDiscreteRegistry = new DiscreteOpsXmlRegistry(context);
-            if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite
-                DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(context);
-                DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry;
-                DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry);
-            }
         }
     }
 
@@ -267,6 +257,19 @@
                 }
             }
         }
+        if (Flags.enableSqliteAppopsAccesses()) {
+            if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) {
+                DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry;
+                DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mContext);
+                DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry);
+            }
+        } else {
+            if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite
+                DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext);
+                DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry;
+                DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry);
+            }
+        }
     }
 
     private boolean isPersistenceInitializedMLocked() {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c125d2d..12b666f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11953,7 +11953,7 @@
         mLoudnessCodecHelper.startLoudnessCodecUpdates(sessionId);
     }
 
-    /** @see LoudnessCodecController#release() */
+    /** @see LoudnessCodecController#close() */
     @Override
     public void stopLoudnessCodecUpdates(int sessionId) {
         mLoudnessCodecHelper.stopLoudnessCodecUpdates(sessionId);
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index c19d2c9..21c9b87 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -16,6 +16,9 @@
 
 package com.android.server.display;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
@@ -1181,6 +1184,14 @@
         }
 
         public RootTaskInfo getFocusedStack() throws RemoteException {
+            if (UserManager.isVisibleBackgroundUsersEnabled()) {
+                // In MUMD (Multiple Users on Multiple Displays) system, the top most focused stack
+                // could be on the secondary display with a user signed on its display so get the
+                // root task info only on the default display.
+                return ActivityTaskManager.getService().getRootTaskInfoOnDisplay(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_UNDEFINED,
+                        Display.DEFAULT_DISPLAY);
+            }
             return ActivityTaskManager.getService().getFocusedRootTaskInfo();
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ca001b9c..3598b9b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -29,6 +29,7 @@
 import static android.Manifest.permission.RESTRICT_DISPLAY_MODES;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.hardware.display.DisplayManagerGlobal.InternalEventFlag;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
@@ -380,6 +381,8 @@
     private final SparseArray<DisplayPowerController> mDisplayPowerControllers =
             new SparseArray<>();
 
+    private int mMaxImportanceForRRCallbacks = IMPORTANCE_VISIBLE;
+
     /** {@link DisplayBlanker} used by all {@link DisplayPowerController}s. */
     private final DisplayBlanker mDisplayBlanker = new DisplayBlanker() {
         // Synchronized to avoid race conditions when updating multiple display states.
@@ -3445,8 +3448,11 @@
     }
 
     private void sendDisplayEventFrameRateOverrideLocked(int displayId) {
+        int event = (mFlags.isFramerateOverrideTriggersRrCallbacksEnabled())
+                ? DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED
+                : DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED;
         Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
-                displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
+                displayId, event);
         mHandler.sendMessage(msg);
     }
 
@@ -3633,6 +3639,7 @@
             pw.println("  mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount);
             pw.println("  mStableDisplaySize=" + mStableDisplaySize);
             pw.println("  mMinimumBrightnessCurve=" + mMinimumBrightnessCurve);
+            pw.println("  mMaxImportanceForRRCallbacks=" + mMaxImportanceForRRCallbacks);
 
             if (mUserPreferredMode != null) {
                 pw.println(" mUserPreferredMode=" + mUserPreferredMode);
@@ -3761,6 +3768,10 @@
         }
     }
 
+    void overrideMaxImportanceForRRCallbacks(int importance) {
+        mMaxImportanceForRRCallbacks = importance;
+    }
+
     boolean requestDisplayPower(int displayId, int requestedState) {
         synchronized (mSyncRoot) {
             final var display = mLogicalDisplayMapper.getDisplayLocked(displayId);
@@ -4144,6 +4155,18 @@
             mPackageName = packageNames == null ? null : packageNames[0];
         }
 
+        public boolean shouldReceiveRefreshRateWithChangeUpdate(int event) {
+            if (mFlags.isRefreshRateEventForForegroundAppsEnabled()
+                    && event == DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED) {
+                int procState = mActivityManagerInternal.getUidProcessState(mUid);
+                int importance = ActivityManager.RunningAppProcessInfo
+                        .procStateToImportance(procState);
+                return importance <= mMaxImportanceForRRCallbacks || mUid <= Process.SYSTEM_UID;
+            }
+
+            return true;
+        }
+
         public void updateEventFlagsMask(@InternalEventFlag long internalEventFlag) {
             mInternalEventFlagsMask.set(internalEventFlag);
         }
@@ -4251,6 +4274,11 @@
                 }
             }
 
+            if (!shouldReceiveRefreshRateWithChangeUpdate(event)) {
+                // The client is not visible to the user and is not a system service, so do nothing.
+                return true;
+            }
+
             try {
                 transmitDisplayEvent(displayId, event);
                 return true;
@@ -4382,26 +4410,34 @@
         // would be unusual to do so.  The method returns true on success.
         // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
         public boolean dispatchPending() {
-            try {
-                synchronized (mCallback) {
-                    if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
-                        return true;
-                    }
-                    if (!isReadyLocked()) {
-                        return false;
-                    }
-                    for (int i = 0; i < mPendingEvents.size(); i++) {
-                        Event displayEvent = mPendingEvents.get(i);
-                        if (DEBUG) {
-                            Slog.d(TAG, "Send pending display event #" + i + " "
-                                    + displayEvent.displayId + "/"
-                                    + displayEvent.event + " to " + mUid + "/" + mPid);
-                        }
-                        transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
-                    }
-                    mPendingEvents.clear();
+            Event[] pending;
+            synchronized (mCallback) {
+                if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
                     return true;
                 }
+                if (!isReadyLocked()) {
+                    return false;
+                }
+                pending = new Event[mPendingEvents.size()];
+                pending = mPendingEvents.toArray(pending);
+                mPendingEvents.clear();
+            }
+            try {
+                for (int i = 0; i < pending.length; i++) {
+                    Event displayEvent = pending[i];
+                    if (DEBUG) {
+                        Slog.d(TAG, "Send pending display event #" + i + " "
+                                + displayEvent.displayId + "/"
+                                + displayEvent.event + " to " + mUid + "/" + mPid);
+                    }
+
+                    if (!shouldReceiveRefreshRateWithChangeUpdate(displayEvent.event)) {
+                        continue;
+                    }
+
+                    transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
+                }
+                return true;
             } catch (RemoteException ex) {
                 Slog.w(TAG, "Failed to notify process "
                         + mPid + " that display topology changed, assuming it died.", ex);
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index f6b2591..e23756f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -112,6 +112,8 @@
                 return requestDisplayPower(Display.STATE_UNKNOWN);
             case "power-off":
                 return requestDisplayPower(Display.STATE_OFF);
+            case "override-max-importance-rr-callbacks":
+                return overrideMaxImportanceForRRCallbacks();
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -631,4 +633,21 @@
         mService.requestDisplayPower(displayId, state);
         return 0;
     }
+
+    private int overrideMaxImportanceForRRCallbacks() {
+        final String importanceString = getNextArg();
+        if (importanceString == null) {
+            getErrPrintWriter().println("Error: no importance specified");
+            return 1;
+        }
+        final int importance;
+        try {
+            importance = Integer.parseInt(importanceString);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: invalid importance: '" + importanceString + "'");
+            return 1;
+        }
+        mService.overrideMaxImportanceForRRCallbacks(importance);
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 43aa6f4..d435144 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -263,6 +263,16 @@
             Flags::baseDensityForExternalDisplays
     );
 
+    private final FlagState mFramerateOverrideTriggersRrCallbacks = new FlagState(
+            Flags.FLAG_FRAMERATE_OVERRIDE_TRIGGERS_RR_CALLBACKS,
+            Flags::framerateOverrideTriggersRrCallbacks
+    );
+
+    private final FlagState mRefreshRateEventForForegroundApps = new FlagState(
+            Flags.FLAG_REFRESH_RATE_EVENT_FOR_FOREGROUND_APPS,
+            Flags::refreshRateEventForForegroundApps
+    );
+
     /**
      * @return {@code true} if 'port' is allowed in display layout configuration file.
      */
@@ -564,6 +574,22 @@
         return mBaseDensityForExternalDisplays.isEnabled();
     }
 
+    /**
+     * @return {@code true} if the flag triggering refresh rate callbacks when framerate is
+     * overridden is enabled
+     */
+    public boolean isFramerateOverrideTriggersRrCallbacksEnabled() {
+        return mFramerateOverrideTriggersRrCallbacks.isEnabled();
+    }
+
+
+    /**
+     * @return {@code true} if the flag for sending refresh rate events only for the apps in
+     * foreground is enabled
+     */
+    public boolean isRefreshRateEventForForegroundAppsEnabled() {
+        return mRefreshRateEventForForegroundApps.isEnabled();
+    }
 
     /**
      * dumps all flagstates
@@ -620,6 +646,8 @@
         pw.println(" " + mSubscribeGranularDisplayEvents);
         pw.println(" " + mEnableDisplayContentModeManagementFlagState);
         pw.println(" " + mBaseDensityForExternalDisplays);
+        pw.println(" " + mFramerateOverrideTriggersRrCallbacks);
+        pw.println(" " + mRefreshRateEventForForegroundApps);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 00a9dcb..63cd2d7 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -479,3 +479,25 @@
     bug: "382954433"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "framerate_override_triggers_rr_callbacks"
+    namespace: "display_manager"
+    description: "Feature flag to trigger the RR callbacks when framerate overridding happens."
+    bug: "390113266"
+    is_fixed_read_only: true
+        metadata {
+          purpose: PURPOSE_BUGFIX
+        }
+}
+
+flag {
+    name: "refresh_rate_event_for_foreground_apps"
+    namespace: "display_manager"
+    description: "Send Refresh Rate events only for the apps in foreground."
+    bug: "390107600"
+    is_fixed_read_only: true
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index e25ea4b..d1f07cb 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -90,6 +90,8 @@
                         (reason) -> updateTouchpadRightClickZoneEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_SYSTEM_GESTURES),
                         (reason) -> updateTouchpadSystemGesturesEnabled()),
+                Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_ACCELERATION_ENABLED),
+                        (reason) -> updateTouchpadAccelerationEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
                         (reason) -> updateShowTouches()),
                 Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION),
@@ -241,6 +243,11 @@
         mNative.setTouchpadSystemGesturesEnabled(InputSettings.useTouchpadSystemGestures(mContext));
     }
 
+    private void updateTouchpadAccelerationEnabled() {
+        mNative.setTouchpadAccelerationEnabled(
+                InputSettings.isTouchpadAccelerationEnabled(mContext));
+    }
+
     private void updateShowTouches() {
         mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
     }
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 4d38c84..f34338a 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -158,6 +158,8 @@
 
     void setTouchpadSystemGesturesEnabled(boolean enabled);
 
+    void setTouchpadAccelerationEnabled(boolean enabled);
+
     void setShowTouches(boolean enabled);
 
     void setNonInteractiveDisplays(int[] displayIds);
@@ -463,6 +465,9 @@
         public native void setTouchpadSystemGesturesEnabled(boolean enabled);
 
         @Override
+        public native void setTouchpadAccelerationEnabled(boolean enabled);
+
+        @Override
         public native void setShowTouches(boolean enabled);
 
         @Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 45c7cff..7b81fc9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2189,7 +2189,7 @@
         if (mVdmInternal == null) {
             mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
         }
-        if (mVdmInternal == null || !android.companion.virtual.flags.Flags.vdmCustomIme()) {
+        if (mVdmInternal == null) {
             return currentMethodId;
         }
 
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 2615a76..2b0ca14 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -334,7 +334,7 @@
         if (Flags.offloadApi() && Flags.offloadImplementation()) {
             HubInfoRegistry registry;
             try {
-                registry = new HubInfoRegistry(mContextHubWrapper);
+                registry = new HubInfoRegistry(mContext, mContextHubWrapper);
                 mEndpointManager =
                         new ContextHubEndpointManager(
                                 mContext, mContextHubWrapper, registry, mTransactionManager);
@@ -821,6 +821,13 @@
         mHubInfoRegistry.unregisterEndpointDiscoveryCallback(callback);
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @Override
+    public void onDiscoveryCallbackFinished() throws RemoteException {
+        super.onDiscoveryCallbackFinished_enforcePermission();
+        mHubInfoRegistry.onDiscoveryCallbackFinished();
+    }
+
     private void checkEndpointDiscoveryPreconditions() {
         if (mHubInfoRegistry == null) {
             Log.e(TAG, "Hub endpoint registry failed to initialize");
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
index bf54fd7..711383b 100644
--- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -16,12 +16,18 @@
 
 package com.android.server.location.contexthub;
 
+import android.content.Context;
 import android.hardware.contexthub.HubEndpointInfo;
 import android.hardware.contexthub.HubServiceInfo;
 import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
 import android.hardware.location.HubInfo;
-import android.os.DeadObjectException;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
@@ -34,10 +40,15 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
 
 class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycleCallback {
     private static final String TAG = "HubInfoRegistry";
+
+    /** The duration of wakelocks acquired during discovery callbacks */
+    private static final long WAKELOCK_TIMEOUT_MILLIS = 5 * 1000;
+
     private final Object mLock = new Object();
 
     private final IContextHubWrapper mContextHubWrapper;
@@ -53,21 +64,37 @@
      * A wrapper class that is used to store arguments to
      * ContextHubManager.registerEndpointCallback.
      */
-    private static class DiscoveryCallback {
+    private static class DiscoveryCallback implements IBinder.DeathRecipient {
+        private final HubInfoRegistry mHubInfoRegistry;
         private final IContextHubEndpointDiscoveryCallback mCallback;
         private final Optional<Long> mEndpointId;
         private final Optional<String> mServiceDescriptor;
 
-        DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, long endpointId) {
+        // True if the binder death recipient fired
+        private final AtomicBoolean mBinderDied = new AtomicBoolean(false);
+
+        DiscoveryCallback(
+                HubInfoRegistry registry,
+                IContextHubEndpointDiscoveryCallback callback,
+                long endpointId)
+                throws RemoteException {
+            mHubInfoRegistry = registry;
             mCallback = callback;
             mEndpointId = Optional.of(endpointId);
             mServiceDescriptor = Optional.empty();
+            attachDeathRecipient();
         }
 
-        DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, String serviceDescriptor) {
+        DiscoveryCallback(
+                HubInfoRegistry registry,
+                IContextHubEndpointDiscoveryCallback callback,
+                String serviceDescriptor)
+                throws RemoteException {
+            mHubInfoRegistry = registry;
             mCallback = callback;
             mEndpointId = Optional.empty();
             mServiceDescriptor = Optional.of(serviceDescriptor);
+            attachDeathRecipient();
         }
 
         public IContextHubEndpointDiscoveryCallback getCallback() {
@@ -79,6 +106,10 @@
          * @return true if info matches
          */
         public boolean isMatch(HubEndpointInfo info) {
+            if (mBinderDied.get()) {
+                Log.w(TAG, "Callback died, isMatch returning false");
+                return false;
+            }
             if (mEndpointId.isPresent()) {
                 return mEndpointId.get() == info.getIdentifier().getEndpoint();
             }
@@ -91,6 +122,17 @@
             }
             return false;
         }
+
+        @Override
+        public void binderDied() {
+            Log.d(TAG, "Binder died for discovery callback");
+            mBinderDied.set(true);
+            mHubInfoRegistry.unregisterEndpointDiscoveryCallback(mCallback);
+        }
+
+        private void attachDeathRecipient() throws RemoteException {
+            mCallback.asBinder().linkToDeath(this, 0 /* flags */);
+        }
     }
 
     /* The list of discovery callbacks registered with the service */
@@ -99,7 +141,11 @@
 
     private final Object mCallbackLock = new Object();
 
-    HubInfoRegistry(IContextHubWrapper contextHubWrapper) throws InstantiationException {
+    /** Wakelock held while endpoint callbacks are being invoked */
+    private final WakeLock mWakeLock;
+
+    HubInfoRegistry(Context context, IContextHubWrapper contextHubWrapper)
+            throws InstantiationException {
         mContextHubWrapper = contextHubWrapper;
         try {
             refreshCachedHubs();
@@ -109,6 +155,16 @@
             Log.e(TAG, error, e);
             throw new InstantiationException(error);
         }
+
+        PowerManager powerManager = context.getSystemService(PowerManager.class);
+        if (powerManager == null) {
+            String error = "PowerManager was null";
+            Log.e(TAG, error);
+            throw new InstantiationError(error);
+        }
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        mWakeLock.setWorkSource(new WorkSource(Process.myUid(), context.getPackageName()));
+        mWakeLock.setReferenceCounted(true);
     }
 
     /** Retrieve the list of hubs available. */
@@ -178,12 +234,7 @@
                     try {
                         cb.onEndpointsStarted(infoList);
                     } catch (RemoteException e) {
-                        if (e instanceof DeadObjectException) {
-                            Log.w(TAG, "onEndpointStarted: callback died, unregistering");
-                            unregisterEndpointDiscoveryCallback(cb);
-                        } else {
-                            Log.e(TAG, "Exception while calling onEndpointsStarted", e);
-                        }
+                        Log.e(TAG, "Exception while calling onEndpointsStarted", e);
                     }
                 });
     }
@@ -208,12 +259,7 @@
                         cb.onEndpointsStopped(
                                 infoList, ContextHubServiceUtil.toAppHubEndpointReason(reason));
                     } catch (RemoteException e) {
-                        if (e instanceof DeadObjectException) {
-                            Log.w(TAG, "onEndpointStopped: callback died, unregistering");
-                            unregisterEndpointDiscoveryCallback(cb);
-                        } else {
-                            Log.e(TAG, "Exception while calling onEndpointsStopped", e);
-                        }
+                        Log.e(TAG, "Exception while calling onEndpointsStopped", e);
                     }
                 });
     }
@@ -254,7 +300,11 @@
         Objects.requireNonNull(callback, "callback cannot be null");
         synchronized (mCallbackLock) {
             checkCallbackAlreadyRegistered(callback);
-            mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, endpointId));
+            try {
+                mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(this, callback, endpointId));
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while adding discovery callback", e);
+            }
         }
     }
 
@@ -264,7 +314,12 @@
         Objects.requireNonNull(callback, "callback cannot be null");
         synchronized (mCallbackLock) {
             checkCallbackAlreadyRegistered(callback);
-            mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, serviceDescriptor));
+            try {
+                mEndpointDiscoveryCallbacks.add(
+                        new DiscoveryCallback(this, callback, serviceDescriptor));
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while adding discovery callback", e);
+            }
         }
     }
 
@@ -282,6 +337,11 @@
         }
     }
 
+    /* package */
+    void onDiscoveryCallbackFinished() {
+        releaseWakeLock();
+    }
+
     private void checkCallbackAlreadyRegistered(
             IContextHubEndpointDiscoveryCallback callback) {
         synchronized (mCallbackLock) {
@@ -315,6 +375,7 @@
                     }
                 }
 
+                acquireWakeLock();
                 consumer.accept(
                         discoveryCallback.getCallback(),
                         infoList.toArray(new HubEndpointInfo[infoList.size()]));
@@ -322,6 +383,26 @@
         }
     }
 
+    private void acquireWakeLock() {
+        Binder.withCleanCallingIdentity(
+                () -> {
+                    mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
+                });
+    }
+
+    private void releaseWakeLock() {
+        Binder.withCleanCallingIdentity(
+                () -> {
+                    if (mWakeLock.isHeld()) {
+                        try {
+                            mWakeLock.release();
+                        } catch (RuntimeException e) {
+                            Log.e(TAG, "Releasing the wakelock fails - ", e);
+                        }
+                    }
+                });
+    }
+
     void dump(IndentingPrintWriter ipw) {
         synchronized (mLock) {
             dumpLocked(ipw);
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index e47cbdc..6ad7ea7 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
@@ -51,6 +52,7 @@
 import android.media.quality.SoundProfileHandle;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.os.RemoteCallbackList;
@@ -69,6 +71,7 @@
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -88,6 +91,10 @@
 
     private static final boolean DEBUG = false;
     private static final String TAG = "MediaQualityService";
+    private static final String ALLOWLIST = "allowlist";
+    private static final String PICTURE_PROFILE_PREFERENCE = "picture_profile_preference";
+    private static final String SOUND_PROFILE_PREFERENCE = "sound_profile_preference";
+    private static final String COMMA_DELIMITER = ",";
     private static final int MAX_UUID_GENERATION_ATTEMPTS = 10;
     private final Context mContext;
     private final MediaQualityDbHelper mMediaQualityDbHelper;
@@ -98,6 +105,8 @@
     private final Map<String, AmbientBacklightCallbackRecord> mCallbackRecords = new HashMap<>();
     private final PackageManager mPackageManager;
     private final SparseArray<UserState> mUserStates = new SparseArray<>();
+    private SharedPreferences mPictureProfileSharedPreference;
+    private SharedPreferences mSoundProfileSharedPreference;
 
     public MediaQualityService(Context context) {
         super(context);
@@ -109,6 +118,19 @@
         mMediaQualityDbHelper = new MediaQualityDbHelper(mContext);
         mMediaQualityDbHelper.setWriteAheadLoggingEnabled(true);
         mMediaQualityDbHelper.setIdleConnectionTimeout(30);
+
+        // The package info in the context isn't initialized in the way it is for normal apps,
+        // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
+        // build the path manually below using the same policy that appears in ContextImpl.
+        final Context deviceContext = mContext.createDeviceProtectedStorageContext();
+        final File pictureProfilePrefs = new File(Environment.getDataSystemDirectory(),
+                PICTURE_PROFILE_PREFERENCE);
+        mPictureProfileSharedPreference = deviceContext.getSharedPreferences(
+                pictureProfilePrefs, Context.MODE_PRIVATE);
+        final File soundProfilePrefs = new File(Environment.getDataSystemDirectory(),
+                SOUND_PROFILE_PREFERENCE);
+        mSoundProfileSharedPreference = deviceContext.getSharedPreferences(
+                soundProfilePrefs, Context.MODE_PRIVATE);
     }
 
     @Override
@@ -1291,6 +1313,11 @@
                 notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
                         Binder.getCallingUid(), Binder.getCallingPid());
             }
+            String allowlist = mPictureProfileSharedPreference.getString(ALLOWLIST, null);
+            if (allowlist != null) {
+                String[] stringArray = allowlist.split(COMMA_DELIMITER);
+                return new ArrayList<>(Arrays.asList(stringArray));
+            }
             return new ArrayList<>();
         }
 
@@ -1300,6 +1327,9 @@
                 notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
                         Binder.getCallingUid(), Binder.getCallingPid());
             }
+            SharedPreferences.Editor editor = mPictureProfileSharedPreference.edit();
+            editor.putString(ALLOWLIST, String.join(COMMA_DELIMITER, packages));
+            editor.commit();
         }
 
         @Override
@@ -1308,6 +1338,11 @@
                 notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
                         Binder.getCallingUid(), Binder.getCallingPid());
             }
+            String allowlist = mSoundProfileSharedPreference.getString(ALLOWLIST, null);
+            if (allowlist != null) {
+                String[] stringArray = allowlist.split(COMMA_DELIMITER);
+                return new ArrayList<>(Arrays.asList(stringArray));
+            }
             return new ArrayList<>();
         }
 
@@ -1317,6 +1352,9 @@
                 notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
                         Binder.getCallingUid(), Binder.getCallingPid());
             }
+            SharedPreferences.Editor editor = mSoundProfileSharedPreference.edit();
+            editor.putString(ALLOWLIST, String.join(COMMA_DELIMITER, packages));
+            editor.commit();
         }
 
         @Override
@@ -1347,7 +1385,7 @@
             try {
                 if (mMediaQuality != null) {
                     if (mMediaQuality.isAutoPqSupported()) {
-                        mMediaQuality.getAutoPqEnabled();
+                        return mMediaQuality.getAutoPqEnabled();
                     }
                 }
             } catch (RemoteException e) {
@@ -1379,7 +1417,7 @@
             try {
                 if (mMediaQuality != null) {
                     if (mMediaQuality.isAutoSrSupported()) {
-                        mMediaQuality.getAutoSrEnabled();
+                        return mMediaQuality.getAutoSrEnabled();
                     }
                 }
             } catch (RemoteException e) {
@@ -1411,7 +1449,7 @@
             try {
                 if (mMediaQuality != null) {
                     if (mMediaQuality.isAutoAqSupported()) {
-                        mMediaQuality.getAutoAqEnabled();
+                        return mMediaQuality.getAutoAqEnabled();
                     }
                 }
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index adf6c1b..588e879 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -356,6 +356,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.TriPredicate;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.modules.expresslog.Counter;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.DeviceIdleInternal;
@@ -762,7 +763,7 @@
 
     private int mWarnRemoteViewsSizeBytes;
     private int mStripRemoteViewsSizeBytes;
-    private String[] mDefaultUnsupportedAdjustments;
+    protected String[] mDefaultUnsupportedAdjustments;
 
     @VisibleForTesting
     protected boolean mShowReviewPermissionsNotification;
@@ -3037,10 +3038,9 @@
         switch(atomTag) {
             case PACKAGE_NOTIFICATION_PREFERENCES:
                 if (notificationClassificationUi()) {
-                    Set<String> pkgs = mAssistants.getPackagesWithKeyTypeAdjustmentSettings();
                     mPreferencesHelper.pullPackagePreferencesStats(data,
                             getAllUsersNotificationPermissions(),
-                            getPackageSpecificAdjustmentKeyTypes(pkgs));
+                            new ArrayMap<>());
                 } else {
                     mPreferencesHelper.pullPackagePreferencesStats(data,
                             getAllUsersNotificationPermissions());
@@ -4378,8 +4378,8 @@
         public @NonNull List<String> getUnsupportedAdjustmentTypes() {
             checkCallerIsSystemOrSystemUiOrShell();
             synchronized (mNotificationLock) {
-                return new ArrayList(mAssistants.mNasUnsupported.getOrDefault(
-                        UserHandle.getUserId(Binder.getCallingUid()), new HashSet<>()));
+                return new ArrayList(mAssistants.getUnsupportedAdjustments(
+                        UserHandle.getUserId(Binder.getCallingUid())));
             }
         }
 
@@ -4401,16 +4401,16 @@
 
         @Override
         @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
-        public @NonNull int[] getAllowedAdjustmentKeyTypesForPackage(String pkg) {
+        public @NonNull String[] getTypeAdjustmentDeniedPackages() {
             checkCallerIsSystemOrSystemUiOrShell();
-            return mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg);
+            return mAssistants.getTypeAdjustmentDeniedPackages();
         }
 
+        @Override
         @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
-        public void setAssistantAdjustmentKeyTypeStateForPackage(String pkg, int type,
-                                                                 boolean enabled) {
+        public void setTypeAdjustmentForPackageState(String pkg, boolean enabled) {
             checkCallerIsSystemOrSystemUiOrShell();
-            mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, type, enabled);
+            mAssistants.setTypeAdjustmentForPackageState(pkg, enabled);
 
             handleSavePolicyFile();
         }
@@ -7137,6 +7137,13 @@
             Slog.e(TAG, "exiting pullStats: bad request");
             return 0;
         }
+
+        @Override
+        public void incrementCounter(String metricId) {
+            if (android.app.Flags.nmBinderPerfLogNmThrottling() && metricId != null) {
+                Counter.logIncrementWithUid(metricId, Binder.getCallingUid());
+            }
+        }
     };
 
     private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) {
@@ -7215,11 +7222,12 @@
                     toRemove.add(potentialKey);
                 }
                 if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) {
+                    mAssistants.setNasUnsupportedDefaults(r.getSbn().getNormalizedUserId());
                     if (!mAssistants.isAdjustmentKeyTypeAllowed(adjustments.getInt(KEY_TYPE))) {
                         toRemove.add(potentialKey);
                     } else if (notificationClassificationUi()
                             && !mAssistants.isTypeAdjustmentAllowedForPackage(
-                            r.getSbn().getPackageName(), adjustments.getInt(KEY_TYPE))) {
+                            r.getSbn().getPackageName())) {
                         toRemove.add(potentialKey);
                     }
                 }
@@ -7556,24 +7564,6 @@
         return allPermissions;
     }
 
-    @VisibleForTesting
-    @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
-    protected @NonNull Map<String, Set<Integer>> getPackageSpecificAdjustmentKeyTypes(
-            Set<String> pkgs) {
-        ArrayMap<String, Set<Integer>> pkgToAllowedTypes = new ArrayMap<>();
-        for (String pkg : pkgs) {
-            int[] allowedTypesArray = mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg);
-            if (allowedTypesArray != null) {
-                Set<Integer> allowedTypes = new ArraySet<Integer>();
-                for (int i : allowedTypesArray) {
-                    allowedTypes.add(i);
-                }
-                pkgToAllowedTypes.append(pkg, allowedTypes);
-            }
-        }
-        return pkgToAllowedTypes;
-    }
-
     private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter,
             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
         JSONObject dump = new JSONObject();
@@ -11886,14 +11876,10 @@
         static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
 
         private static final String ATT_TYPES = "types";
-        private static final String ATT_DENIED = "denied_adjustments";
+        private static final String ATT_DENIED = "user_denied_adjustments";
         private static final String ATT_ENABLED_TYPES = "enabled_key_types";
-        private static final String ATT_NAS_UNSUPPORTED = "unsupported_adjustments";
-        // Encapsulates a list of packages and the bundle types enabled for each package.
-        private static final String TAG_TYPES_ENABLED_FOR_APPS = "types_enabled_for_apps";
-        // Encapsulates the bundle types enabled for a package.
-        private static final String ATT_APP_ENABLED_TYPES = "app_enabled_types";
-        private static final String ATT_PACKAGE = "package";
+        private static final String ATT_NAS_UNSUPPORTED = "nas_unsupported_adjustments";
+        private static final String ATT_TYPES_DENIED_APPS = "types_denied_apps";
 
         private final Object mLock = new Object();
 
@@ -11909,14 +11895,8 @@
         @GuardedBy("mLock")
         private Map<Integer, HashSet<String>> mNasUnsupported = new ArrayMap<>();
 
-        // Types of classifications (aka bundles) enabled/allowed for this package.
-        // If the set is NULL (or package is not in the list), default classification allow list
-        // (the global one) should be used.
-        // If the set is empty, that indicates the package explicitly has all classifications
-        // disallowed.
         @GuardedBy("mLock")
-        private Map<String, Set<Integer>> mClassificationTypePackagesEnabledTypes =
-                new ArrayMap<>();
+        private Set<String> mClassificationTypeDeniedPackages = new ArraySet<>();
 
         protected ComponentName mDefaultFromConfig = null;
 
@@ -11994,9 +11974,6 @@
                 }
             } else {
                 mAllowedAdjustmentKeyTypes.addAll(List.of(DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES));
-                if (mDefaultUnsupportedAdjustments != null) {
-                    mAllowedAdjustments.removeAll(List.of(mDefaultUnsupportedAdjustments));
-                }
             }
         }
 
@@ -12120,104 +12097,41 @@
             }
         }
 
-        /**
-         * Returns whether the type adjustment is allowed for this particular package.
-         * If no package-specific restrictions have been set, defaults to the same value as
-         * isAdjustmentKeyTypeAllowed(type).
-         */
         @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
-        protected boolean isTypeAdjustmentAllowedForPackage(String pkg,
-                                                                     @Adjustment.Types int type) {
+        protected @NonNull boolean isTypeAdjustmentAllowedForPackage(String pkg) {
             synchronized (mLock) {
                 if (notificationClassificationUi()) {
-                    if (mClassificationTypePackagesEnabledTypes.containsKey(pkg)) {
-                        Set<Integer> enabled = mClassificationTypePackagesEnabledTypes.get(pkg);
-                        if (enabled != null) {
-                            return enabled.contains(type);
-                        }
-                    }
-                    // If mClassificationTypePackagesEnabledTypes does not contain the pkg, or
-                    // the stored set is null, return the default.
-                    return isAdjustmentKeyTypeAllowed(type);
+                    return !mClassificationTypeDeniedPackages.contains(pkg);
                 }
             }
-            return false;
+            return true;
         }
 
         @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
-        protected @NonNull Set<String> getPackagesWithKeyTypeAdjustmentSettings() {
-            if (notificationClassificationUi()) {
-                Set<String> packagesWithModifications = new ArraySet<String>();
-                synchronized (mLock) {
-                    for (String pkg : mClassificationTypePackagesEnabledTypes.keySet()) {
-                        if (mClassificationTypePackagesEnabledTypes.get(pkg) != null) {
-                            packagesWithModifications.add(pkg);
-                        }
-                    }
-                }
-                return packagesWithModifications;
-            }
-            return new ArraySet<String>();
-        }
-
-        @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
-        protected @NonNull int[] getAllowedAdjustmentKeyTypesForPackage(String pkg) {
+        protected @NonNull String[] getTypeAdjustmentDeniedPackages() {
             synchronized (mLock) {
                 if (notificationClassificationUi()) {
-                    if (mClassificationTypePackagesEnabledTypes.containsKey(pkg)) {
-                        Set<Integer> enabled = mClassificationTypePackagesEnabledTypes.get(pkg);
-                        if (enabled != null) {
-                            // Convert Set to int[] for return.
-                            int[] returnEnabled = new int[enabled.size()];
-                            int i = 0;
-                            for (int val: enabled) {
-                                returnEnabled[i] = val;
-                                i++;
-                            }
-                            return returnEnabled;
-                        }
-                    }
-                    // If package is not in the map, or the value is null, return the default.
-                    return getAllowedAdjustmentKeyTypes();
+                    return mClassificationTypeDeniedPackages.toArray(new String[0]);
                 }
             }
-            return new int[]{};
+            return new String[]{};
         }
 
         /**
          * Set whether a particular package can have its notification channels adjusted to have a
          * different type by NotificationAssistants.
-         * Note: once this method is called to enable or disable a specific type for a package,
-         * the global default is set as the starting point, and the type is enabled/disabled from
-         * there. Future changes to the global default will not apply automatically to this package.
          */
         @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
-        public void setAssistantAdjustmentKeyTypeStateForPackage(String pkg,
-                                                       @Adjustment.Types int type,
-                                                       boolean enabled) {
+        public void setTypeAdjustmentForPackageState(String pkg, boolean enabled) {
             if (!notificationClassificationUi()) {
                 return;
             }
             synchronized (mLock) {
-                Set<Integer> enabledTypes = null;
-                if (mClassificationTypePackagesEnabledTypes.containsKey(pkg)) {
-                    enabledTypes = mClassificationTypePackagesEnabledTypes.get(pkg);
-                }
-                if (enabledTypes == null) {
-                    // Use global default to start.
-                    enabledTypes = new ArraySet<Integer>();
-                    // Convert from int[] to Set<Integer>
-                    for (int value : getAllowedAdjustmentKeyTypes()) {
-                        enabledTypes.add(value);
-                    }
-                }
-
                 if (enabled) {
-                    enabledTypes.add(type);
+                    mClassificationTypeDeniedPackages.remove(pkg);
                 } else {
-                    enabledTypes.remove(type);
+                    mClassificationTypeDeniedPackages.add(pkg);
                 }
-                mClassificationTypePackagesEnabledTypes.put(pkg, enabledTypes);
             }
         }
 
@@ -12579,7 +12493,7 @@
                 }
             } else {
                 if (android.service.notification.Flags.notificationClassification()) {
-                    mNasUnsupported.put(userId, new HashSet<>());
+                    setNasUnsupportedDefaults(userId);
                 }
             }
             super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
@@ -12620,8 +12534,8 @@
             if (!android.service.notification.Flags.notificationClassification()) {
                 return;
             }
-            HashSet<String> disabledAdjustments =
-                    mNasUnsupported.getOrDefault(info.userid, new HashSet<>());
+            setNasUnsupportedDefaults(info.userid);
+            HashSet<String> disabledAdjustments = mNasUnsupported.get(info.userid);
             if (supported) {
                 disabledAdjustments.remove(key);
             } else {
@@ -12637,7 +12551,15 @@
             if (!android.service.notification.Flags.notificationClassification()) {
                 return new HashSet<>();
             }
-            return mNasUnsupported.getOrDefault(userId, new HashSet<>());
+            setNasUnsupportedDefaults(userId);
+            return mNasUnsupported.get(userId);
+        }
+
+        private void setNasUnsupportedDefaults(@UserIdInt int userId) {
+            if (mNasUnsupported != null && !mNasUnsupported.containsKey(userId)) {
+                mNasUnsupported.put(userId, new HashSet(List.of(mDefaultUnsupportedAdjustments)));
+                handleSavePolicyFile();
+            }
         }
 
         @Override
@@ -12684,25 +12606,16 @@
                         TextUtils.join(",", mAllowedAdjustmentKeyTypes));
                 out.endTag(null, ATT_ENABLED_TYPES);
                 if (notificationClassificationUi()) {
-                    out.startTag(null, TAG_TYPES_ENABLED_FOR_APPS);
-                    for (String pkg: mClassificationTypePackagesEnabledTypes.keySet()) {
-                        Set<Integer> allowedTypes =
-                                mClassificationTypePackagesEnabledTypes.get(pkg);
-                        if (allowedTypes != null) {
-                            out.startTag(null, ATT_APP_ENABLED_TYPES);
-                            out.attribute(null, ATT_PACKAGE, pkg);
-                            out.attribute(null, ATT_TYPES, TextUtils.join(",", allowedTypes));
-                            out.endTag(null, ATT_APP_ENABLED_TYPES);
-                        }
-                    }
-                    out.endTag(null, TAG_TYPES_ENABLED_FOR_APPS);
+                    out.startTag(null, ATT_TYPES_DENIED_APPS);
+                    out.attribute(null, ATT_TYPES,
+                            TextUtils.join(",", mClassificationTypeDeniedPackages));
+                    out.endTag(null, ATT_TYPES_DENIED_APPS);
                 }
             }
         }
 
         @Override
-        protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException,
-                XmlPullParserException {
+        protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException {
             if (!notificationClassification()) {
                 return;
             }
@@ -12729,25 +12642,12 @@
                         }
                     }
                 }
-            } else if (TAG_TYPES_ENABLED_FOR_APPS.equals(tag)) {
-                final int appsOuterDepth = parser.getDepth();
+            } else if (notificationClassificationUi() && ATT_TYPES_DENIED_APPS.equals(tag)) {
+                final String apps = XmlUtils.readStringAttribute(parser, ATT_TYPES);
                 synchronized (mLock) {
-                    mClassificationTypePackagesEnabledTypes.clear();
-                    while (XmlUtils.nextElementWithin(parser, appsOuterDepth)) {
-                        if (!ATT_APP_ENABLED_TYPES.equals(parser.getName())) {
-                            continue;
-                        }
-                        final String app = XmlUtils.readStringAttribute(parser, ATT_PACKAGE);
-                        Set<Integer> allowedTypes = new ArraySet<>();
-                        final String typesString = XmlUtils.readStringAttribute(parser, ATT_TYPES);
-                        if (!TextUtils.isEmpty(typesString)) {
-                            allowedTypes = Arrays.stream(typesString.split(","))
-                                    .map(Integer::valueOf)
-                                    .collect(Collectors.toSet());
-                        }
-                        // Empty type list is allowed, because empty type list signifies the user
-                        // has manually cleared the package of allowed types.
-                        mClassificationTypePackagesEnabledTypes.put(app, allowedTypes);
+                    mClassificationTypeDeniedPackages.clear();
+                    if (!TextUtils.isEmpty(apps)) {
+                        mClassificationTypeDeniedPackages.addAll(Arrays.asList(apps.split(",")));
                     }
                 }
             }
@@ -12772,7 +12672,7 @@
                 List<String> unsupportedAdjustments = new ArrayList(
                         mNasUnsupported.getOrDefault(
                                 UserHandle.getUserId(Binder.getCallingUid()),
-                                new HashSet<>())
+                                new HashSet(List.of(mDefaultUnsupportedAdjustments)))
                 );
                 bundlesAllowed = !unsupportedAdjustments.contains(Adjustment.KEY_TYPE);
             }
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 1aa5ac0..7e853d9 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import android.annotation.Nullable;
 import android.app.NotificationManager;
 import android.content.ComponentName;
 import android.media.AudioManager;
@@ -26,6 +27,7 @@
 import android.service.notification.IConditionProvider;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ConfigOrigin;
 import android.service.notification.ZenModeDiff;
 import android.util.LocalLog;
 
@@ -119,16 +121,17 @@
         append(TYPE_UNSUBSCRIBE, uri + "," + subscribeResult(provider, e));
     }
 
-    public static void traceConfig(String reason, ComponentName triggeringComponent,
-            ZenModeConfig oldConfig, ZenModeConfig newConfig, int callingUid) {
+    public static void traceConfig(@ConfigOrigin int origin, String reason,
+            @Nullable ComponentName triggeringComponent, ZenModeConfig oldConfig,
+            ZenModeConfig newConfig, int callingUid) {
         ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig);
-        if (diff == null || !diff.hasDiff()) {
-            append(TYPE_CONFIG, reason + " no changes");
+        if (!diff.hasDiff()) {
+            append(TYPE_CONFIG, reason + " (" + originToString(origin) + ") no changes");
         } else {
-            append(TYPE_CONFIG, reason
-                    + " - " + triggeringComponent + " : " + callingUid
-                    + ",\n" + (newConfig != null ? newConfig.toString() : null)
-                    + ",\n" + diff);
+            append(TYPE_CONFIG, reason + " (" + originToString(origin) + ") from uid " + callingUid
+                    + (triggeringComponent != null ? " - " + triggeringComponent : "") + ",\n"
+                    + (newConfig != null ? newConfig.toString() : null) + ",\n"
+                    + diff);
         }
     }
 
@@ -241,7 +244,22 @@
         }
     }
 
-    private static String componentToString(ComponentName component) {
+    private static String originToString(@ConfigOrigin int origin) {
+        return switch (origin) {
+            case ZenModeConfig.ORIGIN_UNKNOWN -> "ORIGIN_UNKNOWN";
+            case ZenModeConfig.ORIGIN_INIT -> "ORIGIN_INIT";
+            case ZenModeConfig.ORIGIN_INIT_USER -> "ORIGIN_INIT_USER";
+            case ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI -> "ORIGIN_USER_IN_SYSTEMUI";
+            case ZenModeConfig.ORIGIN_APP -> "ORIGIN_APP";
+            case ZenModeConfig.ORIGIN_SYSTEM -> "ORIGIN_SYSTEM";
+            case ZenModeConfig.ORIGIN_RESTORE_BACKUP -> "ORIGIN_RESTORE_BACKUP";
+            case ZenModeConfig.ORIGIN_USER_IN_APP -> "ORIGIN_USER_IN_APP";
+            default -> origin + "??";
+        };
+    }
+
+    @Nullable
+    private static String componentToString(@Nullable ComponentName component) {
         return component != null ? component.toShortString() : null;
     }
 
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 0a63f3f..b39b6fd 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import static android.app.AutomaticZenRule.TYPE_DRIVING;
 import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
@@ -46,6 +47,7 @@
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.notification.Flags.preventZenDeviceEffectsWhileDriving;
 
 import static java.util.Objects.requireNonNull;
 
@@ -659,7 +661,8 @@
                             mContext.getString(R.string.zen_mode_implicit_deactivated),
                             STATE_FALSE);
                     setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule),
-                            deactivated, ORIGIN_APP, callingUid);
+                            deactivated, ORIGIN_APP,
+                            "applyGlobalZenModeAsImplicitZenRule: " + callingPkg, callingUid);
                 }
             } else {
                 // Either create a new rule with a default ZenPolicy, or update an existing rule's
@@ -971,26 +974,27 @@
             if (Flags.modesApi()) {
                 if (rule != null && canManageAutomaticZenRule(rule, callingUid)) {
                     setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule),
-                            condition, origin, callingUid);
+                            condition, origin, "setAzrState: " + rule.id, callingUid);
                 }
             } else {
                 ArrayList<ZenRule> rules = new ArrayList<>();
                 rules.add(rule); // rule may be null and throw NPE in the next method.
-                setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, callingUid);
+                setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin,
+                        "setAzrState: " + (rule != null ? rule.id : "null!"), callingUid);
             }
         }
     }
 
-    void setAutomaticZenRuleStateFromConditionProvider(UserHandle user, Uri ruleDefinition,
+    void setAutomaticZenRuleStateFromConditionProvider(UserHandle user, Uri ruleConditionId,
             Condition condition, @ConfigOrigin int origin, int callingUid) {
-        checkSetRuleStateOrigin("setAutomaticZenRuleState(Uri ruleDefinition)", origin);
+        checkSetRuleStateOrigin("setAutomaticZenRuleStateFromConditionProvider", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             ZenModeConfig config = getConfigLocked(user);
             if (config == null) return;
             newConfig = config.copy();
 
-            List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleDefinition, condition);
+            List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleConditionId, condition);
             if (Flags.modesApi()) {
                 for (int i = matchingRules.size() - 1; i >= 0; i--) {
                     if (!canManageAutomaticZenRule(matchingRules.get(i), callingUid)) {
@@ -998,13 +1002,14 @@
                     }
                 }
             }
-            setAutomaticZenRuleStateLocked(newConfig, matchingRules, condition, origin, callingUid);
+            setAutomaticZenRuleStateLocked(newConfig, matchingRules, condition, origin,
+                    "setAzrStateFromCps: " + ruleConditionId, callingUid);
         }
     }
 
     @GuardedBy("mConfigLock")
     private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules,
-            Condition condition, @ConfigOrigin int origin, int callingUid) {
+            Condition condition, @ConfigOrigin int origin, String reason, int callingUid) {
         if (rules == null || rules.isEmpty()) return;
 
         if (!Flags.modesUi()) {
@@ -1015,7 +1020,7 @@
 
         for (ZenRule rule : rules) {
             applyConditionAndReconsiderOverride(rule, condition, origin);
-            setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid);
+            setConfigLocked(config, rule.component, origin, reason, callingUid);
         }
     }
 
@@ -2111,13 +2116,14 @@
     }
 
     @GuardedBy("mConfigLock")
-    private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
-            @ConfigOrigin int origin, String reason, int callingUid) {
+    private boolean setConfigLocked(ZenModeConfig config,
+            @Nullable ComponentName triggeringComponent, @ConfigOrigin int origin, String reason,
+            int callingUid) {
         return setConfigLocked(config, origin, reason, triggeringComponent, true /*setRingerMode*/,
                 callingUid);
     }
 
-    void setConfig(ZenModeConfig config, ComponentName triggeringComponent,
+    void setConfig(ZenModeConfig config, @Nullable ComponentName triggeringComponent,
             @ConfigOrigin int origin, String reason, int callingUid) {
         synchronized (mConfigLock) {
             setConfigLocked(config, triggeringComponent, origin, reason, callingUid);
@@ -2126,7 +2132,7 @@
 
     @GuardedBy("mConfigLock")
     private boolean setConfigLocked(ZenModeConfig config, @ConfigOrigin int origin,
-            String reason, ComponentName triggeringComponent, boolean setRingerMode,
+            String reason, @Nullable ComponentName triggeringComponent, boolean setRingerMode,
             int callingUid) {
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -2149,7 +2155,7 @@
                 mConfigs.put(config.user, config);
             }
             if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
-            ZenLog.traceConfig(reason, triggeringComponent, mConfig, config, callingUid);
+            ZenLog.traceConfig(origin, reason, triggeringComponent, mConfig, config, callingUid);
 
             // send some broadcasts
             Policy newPolicy = getNotificationPolicy(config);
@@ -2375,6 +2381,26 @@
             }
 
             if (Flags.modesApi()) {
+                // Prevent other rules from applying grayscale if Driving is active (but allow it
+                // if _Driving itself_ wants grayscale).
+                if (Flags.modesUi() && preventZenDeviceEffectsWhileDriving()) {
+                    boolean hasActiveDriving = false;
+                    boolean hasActiveDrivingWithGrayscale = false;
+                    for (ZenRule rule : mConfig.automaticRules.values()) {
+                        if (rule.isActive() && rule.type == TYPE_DRIVING) {
+                            hasActiveDriving = true;
+                            if (rule.zenDeviceEffects != null
+                                    && rule.zenDeviceEffects.shouldDisplayGrayscale()) {
+                                hasActiveDrivingWithGrayscale = true;
+                                break; // Further rules won't affect decision.
+                            }
+                        }
+                    }
+                    if (hasActiveDriving && !hasActiveDrivingWithGrayscale) {
+                        deviceEffectsBuilder.setShouldDisplayGrayscale(false);
+                    }
+                }
+
                 ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
                 if (!deviceEffects.equals(mConsolidatedDeviceEffects)) {
                     mConsolidatedDeviceEffects = deviceEffects;
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index b4a8aee..822ff48 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -190,3 +190,13 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "prevent_zen_device_effects_while_driving"
+  namespace: "systemui"
+  description: "Don't apply certain device effects (such as grayscale) from active zen rules, if a rule of TYPE_DRIVING is active"
+  bug: "390389174"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index f23d782..33c1229 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -137,16 +137,26 @@
             return;
         }
 
-        String processName = "UNKNOWN";
         final boolean isProtoFile = filename.endsWith(".pb");
+
+        // Only process the pb tombstone output, the text version will be generated in
+        // BootReceiver.filterAndAddTombstoneToDropBox through pbtombstone
+        if (Flags.protoTombstone() && !isProtoFile) {
+            return;
+        }
+
         File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
 
-        Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
-        if (parsedTombstone.isPresent()) {
-            processName = parsedTombstone.get().getProcessName();
-        }
-        BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
+        final String processName = handleProtoTombstone(protoPath, isProtoFile)
+                .map(TombstoneFile::getProcessName)
+                .orElse("UNKNOWN");
 
+        if (Flags.protoTombstone()) {
+            BootReceiver.filterAndAddTombstoneToDropBox(mContext, path, processName, mTmpFileLock);
+        } else {
+            BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile,
+                    processName, mTmpFileLock);
+        }
         // TODO(b/339371242): An optimizer on WearOS is misbehaving and this member is being garbage
         // collected as it's never referenced inside this class outside of the constructor. But,
         // it's a file watcher, and needs to stay alive to do its job. So, add a cheap check here to
diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig
index efdc9b8..5e35cf5 100644
--- a/services/core/java/com/android/server/os/core_os_flags.aconfig
+++ b/services/core/java/com/android/server/os/core_os_flags.aconfig
@@ -3,7 +3,7 @@
 
 flag {
     name: "proto_tombstone"
-    namespace: "proto_tombstone_ns"
+    namespace: "stability"
     description: "Use proto tombstones as source of truth for adding to dropbox"
     bug: "323857385"
 }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4cca855..8eb5b6f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -856,39 +856,45 @@
         if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
 
         final boolean succeeded = request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED;
-        if (succeeded && doRestore) {
-            // Pass responsibility to the Backup Manager.  It will perform a
-            // restore if appropriate, then pass responsibility back to the
-            // Package Manager to run the post-install observer callbacks
-            // and broadcasts.
-            request.closeFreezer();
-            doRestore = performBackupManagerRestore(userId, token, request);
-        }
+        if (succeeded) {
+            request.onRestoreStarted();
+            if (doRestore) {
+                // Pass responsibility to the Backup Manager.  It will perform a
+                // restore if appropriate, then pass responsibility back to the
+                // Package Manager to run the post-install observer callbacks
+                // and broadcasts.
+                // Note: MUST close freezer before backup/restore, otherwise test
+                // of CtsBackupHostTestCases will fail.
+                request.closeFreezer();
+                doRestore = performBackupManagerRestore(userId, token, request);
+            }
 
-        // If this is an update to a package that might be potentially downgraded, then we
-        // need to check with the rollback manager whether there's any userdata that might
-        // need to be snapshotted or restored for the package.
-        //
-        // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
-        if (succeeded && !doRestore && update) {
-            doRestore = performRollbackManagerRestore(userId, token, request);
-        }
+            // If this is an update to a package that might be potentially downgraded, then we
+            // need to check with the rollback manager whether there's any userdata that might
+            // need to be snapshotted or restored for the package.
+            //
+            // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
+            if (!doRestore && update) {
+                doRestore = performRollbackManagerRestore(userId, token, request);
+            }
 
-        if (succeeded && doRestore && !request.hasPostInstallRunnable()) {
-            boolean hasNeverBeenRestored =
-                    packageSetting != null && packageSetting.isPendingRestore();
-            request.setPostInstallRunnable(() -> {
-                // Permissions should be restored on each user that has the app installed for the
-                // first time, unless it's an unarchive install for an archived app, in which case
-                // the permissions should be restored on each user that has the app updated.
-                int[] userIdsToRestorePermissions = hasNeverBeenRestored
-                        ? request.getUpdateBroadcastUserIds()
-                        : request.getFirstTimeBroadcastUserIds();
-                for (int restorePermissionUserId : userIdsToRestorePermissions) {
-                    mPm.restorePermissionsAndUpdateRolesForNewUserInstall(request.getName(),
-                            restorePermissionUserId);
-                }
-            });
+            if (doRestore && !request.hasPostInstallRunnable()) {
+                boolean hasNeverBeenRestored =
+                        packageSetting != null && packageSetting.isPendingRestore();
+                request.setPostInstallRunnable(() -> {
+                    // Permissions should be restored on each user that has the app installed for
+                    // the first time, unless it's an unarchive install for an archived app, in
+                    // which case the permissions should be restored on each user that has the
+                    // app updated.
+                    int[] userIdsToRestorePermissions = hasNeverBeenRestored
+                            ? request.getUpdateBroadcastUserIds()
+                            : request.getFirstTimeBroadcastUserIds();
+                    for (int restorePermissionUserId : userIdsToRestorePermissions) {
+                        mPm.restorePermissionsAndUpdateRolesForNewUserInstall(request.getName(),
+                                restorePermissionUserId);
+                    }
+                });
+            }
         }
 
         if (doRestore) {
@@ -898,8 +904,11 @@
                 }
             }
         } else {
-            // No restore possible, or the Backup Manager was mysteriously not
-            // available -- just fire the post-install work request directly.
+            // No restore possible, or the Backup Manager was mysteriously not available.
+            // we don't need to wait for restore to complete before closing the freezer,
+            // so we can close the freezer right away.
+            // Also just fire the post-install work request directly.
+            request.closeFreezer();
             if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
 
             Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index c96c160..fbf5db5 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -1016,6 +1016,18 @@
         }
     }
 
+    public void onRestoreStarted() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepStarted(PackageMetrics.STEP_RESTORE);
+        }
+    }
+
+    public void onRestoreFinished() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepFinished(PackageMetrics.STEP_RESTORE);
+        }
+    }
+
     public void onDexoptFinished(DexoptResult dexoptResult) {
         // Only report external profile warnings when installing from adb. The goal is to warn app
         // developers if they have provided bad external profiles, so it's not beneficial to report
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 9916be6..7b1eb58 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -99,6 +99,7 @@
                     }
                     break;
                 }
+                request.onRestoreFinished();
                 request.closeFreezer();
                 request.onInstallCompleted();
                 request.runPostInstallRunnable();
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 856d6a7..994ee42 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -72,6 +72,7 @@
     public static final int STEP_COMMIT = 4;
     public static final int STEP_DEXOPT = 5;
     public static final int STEP_FREEZE_INSTALL = 6;
+    public static final int STEP_RESTORE = 7;
 
     @IntDef(prefix = {"STEP_"}, value = {
             STEP_PREPARE,
@@ -79,7 +80,8 @@
             STEP_RECONCILE,
             STEP_COMMIT,
             STEP_DEXOPT,
-            STEP_FREEZE_INSTALL
+            STEP_FREEZE_INSTALL,
+            STEP_RESTORE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface StepInt {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 9d840d0..b905041 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -26,6 +26,7 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
 import static android.permission.flags.Flags.serverSideAttributionRegistration;
@@ -1668,7 +1669,22 @@
                         throw new SecurityException(msg + ":" + e.getMessage());
                     }
                 }
-                return Math.max(checkedOpResult, notedOpResult);
+                int result = Math.max(checkedOpResult, notedOpResult);
+                // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+                if (op == OP_BLUETOOTH_CONNECT && result == MODE_ERRORED) {
+                    if (result == checkedOpResult) {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+                                + " checkOp for resolvedAttributionSource "
+                                + resolvedAttributionSource + " and op " + op
+                                + " returned MODE_ERRORED");
+                    } else {
+                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+                                + " noteOp for resolvedAttributionSource "
+                                + resolvedAttributionSource + " and op " + notedOp
+                                + " returned MODE_ERRORED");
+                    }
+                }
+                return result;
             }
         }
 
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index c1fad33..a1531a8 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -2,6 +2,8 @@
 santoscordon@google.com
 petsjonkin@google.com
 brup@google.com
+flc@google.com
+wilczynskip@google.com
 
 per-file ThermalManagerService.java=file:/THERMAL_OWNERS
 per-file LowPowerStandbyController.java=qingxun@google.com
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 36c337c..7f2c68f 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -4899,7 +4899,7 @@
             Slog.e(TAG, "Disconnected from keystore service. Cannot pull.", e);
             return StatsManager.PULL_SKIP;
         } catch (ServiceSpecificException e) {
-            Slog.e(TAG, "pulling keystore metrics failed", e);
+            Slog.e(TAG, "Pulling keystore atom with tag " + atomTag + " failed", e);
             return StatsManager.PULL_SKIP;
         } finally {
             Binder.restoreCallingIdentity(callingToken);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index b7b4cc0..48dd2eb 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -87,6 +87,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
+import android.util.IntArray;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -124,6 +125,7 @@
 import com.android.server.power.ShutdownCheckPoints;
 import com.android.server.power.ShutdownThread;
 import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.systemui.shared.Flags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -341,15 +343,19 @@
 
     @Override
     public void onDisplayAdded(int displayId) {
-        synchronized (mLock) {
-            mDisplayUiState.put(displayId, new UiState());
+        if (Flags.statusBarConnectedDisplays()) {
+            synchronized (mLock) {
+                mDisplayUiState.put(displayId, new UiState());
+            }
         }
     }
 
     @Override
     public void onDisplayRemoved(int displayId) {
-        synchronized (mLock) {
-            mDisplayUiState.remove(displayId);
+        if (Flags.statusBarConnectedDisplays()) {
+            synchronized (mLock) {
+                mDisplayUiState.remove(displayId);
+            }
         }
     }
 
@@ -1320,53 +1326,66 @@
         return mTracingEnabled;
     }
 
-    // TODO(b/117478341): make it aware of multi-display if needed.
     @Override
     public void disable(int what, IBinder token, String pkg) {
         disableForUser(what, token, pkg, mCurrentUserId);
     }
 
-    // TODO(b/117478341): make it aware of multi-display if needed.
+    /**
+     * Disable additional status bar features for user for all displays. Pass the bitwise-or of the
+     * {@code #DISABLE_*} flags. To re-enable everything, pass {@code #DISABLE_NONE}.
+     *
+     * Warning: Only pass {@code #DISABLE_*} flags into this function, do not use
+     * {@code #DISABLE2_*} flags.
+     */
     @Override
     public void disableForUser(int what, IBinder token, String pkg, int userId) {
         enforceStatusBar();
         enforceValidCallingUser();
 
         synchronized (mLock) {
-            disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1);
+            IntArray displayIds = new IntArray();
+            for (int i = 0; i < mDisplayUiState.size(); i++) {
+                displayIds.add(mDisplayUiState.keyAt(i));
+            }
+            disableLocked(displayIds, userId, what, token, pkg, 1);
         }
     }
 
-    // TODO(b/117478341): make it aware of multi-display if needed.
     /**
-     * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags.
-     * To re-enable everything, pass {@link #DISABLE2_NONE}.
+     * Disable additional status bar features. Pass the bitwise-or of the {@code #DISABLE2_*} flags.
+     * To re-enable everything, pass {@code #DISABLE2_NONE}.
      *
-     * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+     * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
+     * {@code #DISABLE_*} flags.
      */
     @Override
     public void disable2(int what, IBinder token, String pkg) {
         disable2ForUser(what, token, pkg, mCurrentUserId);
     }
 
-    // TODO(b/117478341): make it aware of multi-display if needed.
     /**
-     * Disable additional status bar features for a given user. Pass the bitwise-or of the
-     * DISABLE2_* flags. To re-enable everything, pass {@link #DISABLE_NONE}.
+     * Disable additional status bar features for a given user for all displays. Pass the bitwise-or
+     * of the {@code #DISABLE2_*} flags. To re-enable everything, pass {@code #DISABLE2_NONE}.
      *
-     * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+     * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
+     * {@code #DISABLE_*}  flags.
      */
     @Override
     public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
         enforceStatusBar();
 
         synchronized (mLock) {
-            disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 2);
+            IntArray displayIds = new IntArray();
+            for (int i = 0; i < mDisplayUiState.size(); i++) {
+                displayIds.add(mDisplayUiState.keyAt(i));
+            }
+            disableLocked(displayIds, userId, what, token, pkg, 2);
         }
     }
 
-    private void disableLocked(int displayId, int userId, int what, IBinder token, String pkg,
-            int whichFlag) {
+    private void disableLocked(IntArray displayIds, int userId, int what, IBinder token,
+            String pkg, int whichFlag) {
         // It's important that the the callback and the call to mBar get done
         // in the same order when multiple threads are calling this function
         // so they are paired correctly.  The messages on the handler will be
@@ -1376,18 +1395,27 @@
         // Ensure state for the current user is applied, even if passed a non-current user.
         final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1);
         final int net2 = gatherDisableActionsLocked(mCurrentUserId, 2);
-        final UiState state = getUiState(displayId);
-        if (!state.disableEquals(net1, net2)) {
-            state.setDisabled(net1, net2);
-            mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
-            IStatusBar bar = mBar;
-            if (bar != null) {
-                try {
-                    bar.disable(displayId, net1, net2);
-                } catch (RemoteException ex) {
+        boolean shouldCallNotificationOnSetDisabled = false;
+        IStatusBar bar = mBar;
+        for (int displayId : displayIds.toArray()) {
+            final UiState state = getUiState(displayId);
+            if (!state.disableEquals(net1, net2)) {
+                shouldCallNotificationOnSetDisabled = true;
+                state.setDisabled(net1, net2);
+                if (bar != null) {
+                    try {
+                        // TODO(b/388244660): Create IStatusBar#disableForAllDisplays to avoid
+                        // multiple IPC calls.
+                        bar.disable(displayId, net1, net2);
+                    } catch (RemoteException ex) {
+                        Slog.e(TAG, "Unable to disable Status bar.", ex);
+                    }
                 }
             }
         }
+        if (shouldCallNotificationOnSetDisabled) {
+            mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
+        }
     }
 
     /**
@@ -1539,7 +1567,8 @@
         if (SPEW) Slog.d(TAG, "setDisableFlags(0x" + Integer.toHexString(flags) + ")");
 
         synchronized (mLock) {
-            disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1);
+            disableLocked(IntArray.wrap(new int[]{displayId}), mCurrentUserId, flags,
+                    mSysUiVisToken, cause, 1);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0226650..f4870d5 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -406,7 +406,7 @@
 /**
  * An entry in the history task, representing an activity.
  */
-final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
+final class ActivityRecord extends WindowToken {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_ATM;
     private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
     private static final String TAG_APP = TAG + POSTFIX_APP;
@@ -736,9 +736,6 @@
      */
     boolean mAllowCrossUidActivitySwitchFromBelow;
 
-    /** Have we been asked to have this token keep the screen frozen? */
-    private boolean mFreezingScreen;
-
     // These are used for determining when all windows associated with
     // an activity have been drawn, so they can be made visible together
     // at the same time.
@@ -784,11 +781,6 @@
     @NonNull
     final AppCompatController mAppCompatController;
 
-    // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
-    // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
-    // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
-    private boolean mIsEligibleForFixedOrientationLetterbox;
-
     /**
      * Whether the activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
      */
@@ -2829,7 +2821,10 @@
     }
 
     void removeStartingWindow() {
-        boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation();
+        final AppCompatLetterboxPolicy letterboxPolicy = mAppCompatController
+                .getAppCompatLetterboxPolicy();
+        boolean prevEligibleForLetterboxEducation =
+                letterboxPolicy.isEligibleForLetterboxEducation();
 
         if (mStartingData != null
                 && mStartingData.mRemoveAfterTransaction == AFTER_TRANSITION_FINISH) {
@@ -2842,8 +2837,8 @@
         removeStartingWindowAnimation(true /* prepareAnimation */);
 
         final Task task = getTask();
-        if (prevEligibleForLetterboxEducation != isEligibleForLetterboxEducation()
-                && task != null) {
+        if (task != null && prevEligibleForLetterboxEducation
+                != letterboxPolicy.isEligibleForLetterboxEducation()) {
             // Trigger TaskInfoChanged to update the letterbox education.
             task.dispatchTaskInfoChangedIfNeeded(true /* force */);
         }
@@ -4513,8 +4508,6 @@
             removeIfPossible();
         }
 
-        stopFreezingScreen(true, true);
-
         final DisplayContent dc = getDisplayContent();
         if (dc.mFocusedApp == this) {
             ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
@@ -5795,9 +5788,7 @@
                         + " visibleRequested=%b, isInTransition=%b, runningAnimation=%b, caller=%s",
                 this, isVisible(), mVisibleRequested, isInTransition(), runningAnimation,
                 Debug.getCallers(5));
-        if (!visible) {
-            stopFreezingScreen(true, true);
-        } else {
+        if (visible) {
             // If we are being set visible, and the starting window is not yet displayed,
             // then make sure it doesn't get displayed.
             if (mStartingWindow != null && !mStartingWindow.isDrawn()
@@ -5805,9 +5796,6 @@
                 mStartingWindow.clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
                 mStartingWindow.mLegacyPolicyVisibilityAfterAnim = false;
             }
-            // We are becoming visible, so better freeze the screen with the windows that are
-            // getting visible so we also wait for them.
-            forAllWindows(mWmService::makeWindowFreezingScreenIfNeededLocked, true);
         }
         // dispatchTaskInfoChangedIfNeeded() right after ActivityRecord#setVisibility() can report
         // the stale visible state, because the state will be updated after the app transition.
@@ -6844,123 +6832,6 @@
         rootTask.removeLaunchTickMessages();
     }
 
-    boolean mayFreezeScreenLocked() {
-        return mayFreezeScreenLocked(app);
-    }
-
-    private boolean mayFreezeScreenLocked(WindowProcessController app) {
-        // Only freeze the screen if this activity is currently attached to
-        // an application, and that application is not blocked or unresponding.
-        // In any other case, we can't count on getting the screen unfrozen,
-        // so it is best to leave as-is.
-        return hasProcess() && !app.isCrashing() && !app.isNotResponding();
-    }
-
-    void startFreezingScreenLocked(WindowProcessController app, int configChanges) {
-        if (mayFreezeScreenLocked(app)) {
-            if (getParent() == null) {
-                Slog.w(TAG_WM,
-                        "Attempted to freeze screen with non-existing app token: " + token);
-                return;
-            }
-
-            // Window configuration changes only effect windows, so don't require a screen freeze.
-            int freezableConfigChanges = configChanges & ~(CONFIG_WINDOW_CONFIGURATION);
-            if (freezableConfigChanges == 0 && okToDisplay()) {
-                ProtoLog.v(WM_DEBUG_ORIENTATION, "Skipping set freeze of %s", token);
-                return;
-            }
-
-            startFreezingScreen();
-        }
-    }
-
-    void startFreezingScreen() {
-        startFreezingScreen(ROTATION_UNDEFINED /* overrideOriginalDisplayRotation */);
-    }
-
-    void startFreezingScreen(int overrideOriginalDisplayRotation) {
-        if (mTransitionController.isShellTransitionsEnabled()) {
-            return;
-        }
-        ProtoLog.i(WM_DEBUG_ORIENTATION,
-                "Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s",
-                token, isVisible(), mFreezingScreen, mVisibleRequested,
-                new RuntimeException().fillInStackTrace());
-        if (!mVisibleRequested) {
-            return;
-        }
-
-        // If the override is given, the rotation of display doesn't change but we still want to
-        // cover the activity whose configuration is changing by freezing the display and running
-        // the rotation animation.
-        final boolean forceRotation = overrideOriginalDisplayRotation != ROTATION_UNDEFINED;
-        if (!mFreezingScreen) {
-            mFreezingScreen = true;
-            mWmService.registerAppFreezeListener(this);
-            mWmService.mAppsFreezingScreen++;
-            if (mWmService.mAppsFreezingScreen == 1) {
-                if (forceRotation) {
-                    // Make sure normal rotation animation will be applied.
-                    mDisplayContent.getDisplayRotation().cancelSeamlessRotation();
-                }
-                mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */,
-                        mDisplayContent, overrideOriginalDisplayRotation);
-                mWmService.mH.removeMessages(H.APP_FREEZE_TIMEOUT);
-                mWmService.mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
-            }
-        }
-        if (forceRotation) {
-            // The rotation of the real display won't change, so in order to unfreeze the screen
-            // via {@link #checkAppWindowsReadyToShow}, the windows have to be able to call
-            // {@link WindowState#reportResized} (it is skipped if the window is freezing) to update
-            // the drawn state.
-            return;
-        }
-        final int count = mChildren.size();
-        for (int i = 0; i < count; i++) {
-            final WindowState w = mChildren.get(i);
-            w.onStartFreezingScreen();
-        }
-    }
-
-    boolean isFreezingScreen() {
-        return mFreezingScreen;
-    }
-
-    @Override
-    public void onAppFreezeTimeout() {
-        Slog.w(TAG_WM, "Force clearing freeze: " + this);
-        stopFreezingScreen(true, true);
-    }
-
-    void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) {
-        if (!mFreezingScreen) {
-            return;
-        }
-        ProtoLog.v(WM_DEBUG_ORIENTATION,
-                "Clear freezing of %s force=%b", this, force);
-        final int count = mChildren.size();
-        boolean unfrozeWindows = false;
-        for (int i = 0; i < count; i++) {
-            final WindowState w = mChildren.get(i);
-            unfrozeWindows |= w.onStopFreezingScreen();
-        }
-        if (force || unfrozeWindows) {
-            ProtoLog.v(WM_DEBUG_ORIENTATION, "No longer freezing: %s", this);
-            mFreezingScreen = false;
-            mWmService.unregisterAppFreezeListener(this);
-            mWmService.mAppsFreezingScreen--;
-            mWmService.mLastFinishedFreezeSource = this;
-        }
-        if (unfreezeSurfaceNow) {
-            if (unfrozeWindows) {
-                mWmService.mWindowPlacerLocked.performSurfacePlacement();
-            }
-            mWmService.stopFreezingDisplayLocked();
-        }
-    }
-
     void onFirstWindowDrawn(WindowState win) {
         firstWindowDrawn = true;
         // stop tracking
@@ -7103,24 +6974,11 @@
             return;
         }
 
-        // The token has now changed state to having all windows shown...  what to do, what to do?
-        if (mFreezingScreen) {
-            showAllWindowsLocked();
-            stopFreezingScreen(false, true);
-            ProtoLog.i(WM_DEBUG_ORIENTATION,
-                    "Setting mOrientationChangeComplete=true because wtoken %s "
-                            + "numInteresting=%d numDrawn=%d",
-                    this, mNumInterestingWindows, mNumDrawnWindows);
-            // This will set mOrientationChangeComplete and cause a pass through layout.
-            setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
-                    "checkAppWindowsReadyToShow: freezingScreen");
-        } else {
-            setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
+        setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
 
-            // We can now show all of the drawn windows!
-            if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) {
-                showAllWindowsLocked();
-            }
+        // We can now show all of the drawn windows!
+        if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) {
+            showAllWindowsLocked();
         }
     }
 
@@ -7206,10 +7064,10 @@
 
         if (DEBUG_STARTING_WINDOW_VERBOSE && w == mStartingWindow) {
             Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen()
-                    + " allDrawn=" + allDrawn + " freezingScreen=" + mFreezingScreen);
+                    + " allDrawn=" + allDrawn);
         }
 
-        if (allDrawn && !mFreezingScreen) {
+        if (allDrawn) {
             return false;
         }
 
@@ -7250,10 +7108,8 @@
                         mNumDrawnWindows++;
 
                         if (DEBUG_VISIBILITY || WM_DEBUG_ORIENTATION.isLogToLogcat()) {
-                            Slog.v(TAG, "tokenMayBeDrawn: "
-                                    + this + " w=" + w + " numInteresting=" + mNumInterestingWindows
-                                    + " freezingScreen=" + mFreezingScreen
-                                    + " mAppFreezing=" + w.mAppFreezing);
+                            Slog.v(TAG, "tokenMayBeDrawn: " + this + " w=" + w
+                                    + " numInteresting=" + mNumInterestingWindows);
                         }
 
                         isInterestingAndDrawn = true;
@@ -8201,8 +8057,6 @@
         }
 
         mDisplayContent.mPinnedTaskController.onCancelFixedRotationTransform();
-        // Perform rotation animation according to the rotation of this activity.
-        startFreezingScreen(originalDisplayRotation);
         // This activity may relaunch or perform configuration change so once it has reported drawn,
         // the screen can be unfrozen.
         ensureActivityConfiguration();
@@ -8440,9 +8294,11 @@
             mTmpConfig.updateFrom(resolvedConfig);
             newParentConfiguration = mTmpConfig;
         }
-
-        mAppCompatController.getAspectRatioPolicy().reset();
-        mIsEligibleForFixedOrientationLetterbox = false;
+        final AppCompatAspectRatioPolicy aspectRatioPolicy =
+                mAppCompatController.getAspectRatioPolicy();
+        aspectRatioPolicy.reset();
+        mAppCompatController.getAppCompatLetterboxPolicy()
+                .resetFixedOrientationLetterboxEligibility();
         mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration,
                 isFixedRotationTransforming());
 
@@ -8472,12 +8328,7 @@
         // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
         // are already calculated in resolveFixedOrientationConfiguration.
         // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
-        if (!mAppCompatController.getAspectRatioPolicy()
-                    .isLetterboxedForFixedOrientationAndAspectRatio()
-                && !mAppCompatController.getAspectRatioOverrides()
-                    .hasFullscreenOverride()) {
-            resolveAspectRatioRestriction(newParentConfiguration);
-        }
+        aspectRatioPolicy.resolveAspectRatioRestrictionIfNeeded(newParentConfiguration);
         final AppCompatDisplayInsets appCompatDisplayInsets = getAppCompatDisplayInsets();
         final AppCompatSizeCompatModePolicy scmPolicy =
                 mAppCompatController.getSizeCompatModePolicy();
@@ -8509,8 +8360,7 @@
                 // Fixed orientation letterboxing is possible on both large screen devices
                 // with ignoreOrientationRequest enabled and on phones in split screen even with
                 // ignoreOrientationRequest disabled.
-                && (mAppCompatController.getAspectRatioPolicy()
-                    .isLetterboxedForFixedOrientationAndAspectRatio()
+                && (aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio()
                         // Limiting check for aspect ratio letterboxing to devices with enabled
                         // ignoreOrientationRequest. This avoids affecting phones where apps may
                         // not expect the change of smallestScreenWidthDp after rotation which is
@@ -8518,7 +8368,7 @@
                         // accurate on phones shouldn't make the big difference and is expected
                         // to be already well-tested by apps.
                         || (isIgnoreOrientationRequest
-                && mAppCompatController.getAspectRatioPolicy().isAspectRatioApplied()))) {
+                && aspectRatioPolicy.isAspectRatioApplied()))) {
             // TODO(b/264034555): Use mDisplayContent to calculate smallestScreenWidthDp from all
             // rotations and only re-calculate if parent bounds have non-orientation size change.
             resolvedConfig.smallestScreenWidthDp =
@@ -8785,28 +8635,6 @@
     }
 
     /**
-     * Whether this activity is eligible for letterbox eduction.
-     *
-     * <p>Conditions that need to be met:
-     *
-     * <ul>
-     *     <li>{@link AppCompatConfiguration#getIsEducationEnabled} is true.
-     *     <li>The activity is eligible for fixed orientation letterbox.
-     *     <li>The activity is in fullscreen.
-     *     <li>The activity is portrait-only.
-     *     <li>The activity doesn't have a starting window (education should only be displayed
-     *     once the starting window is removed in {@link #removeStartingWindow}).
-     * </ul>
-     */
-    boolean isEligibleForLetterboxEducation() {
-        return mWmService.mAppCompatConfiguration.getIsEducationEnabled()
-                && mIsEligibleForFixedOrientationLetterbox
-                && getWindowingMode() == WINDOWING_MODE_FULLSCREEN
-                && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT
-                && mStartingWindow == null;
-    }
-
-    /**
      * In some cases, applying insets to bounds changes the orientation. For example, if a
      * close-to-square display rotates to portrait to respect a portrait orientation activity, after
      * insets such as the status and nav bars are applied, the activity may actually have a
@@ -8910,11 +8738,11 @@
         // If the activity requires a different orientation (either by override or activityInfo),
         // make it fit the available bounds by scaling down its bounds.
         final int forcedOrientation = getRequestedConfigurationOrientation();
+        final boolean isEligibleForFixedOrientationLetterbox = mAppCompatController
+                .getAppCompatLetterboxPolicy()
+                .resolveFixedOrientationLetterboxEligibility(forcedOrientation, parentOrientation);
 
-        mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED
-                && forcedOrientation != parentOrientation;
-
-        if (!mIsEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED
+        if (!isEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED
                 || orientationRespectedWithInsets)) {
             return;
         }
@@ -9007,37 +8835,6 @@
                 new Rect(resolvedBounds));
     }
 
-    /**
-     * Resolves aspect ratio restrictions for an activity. If the bounds are restricted by
-     * aspect ratio, the position will be adjusted later in {@link #updateResolvedBoundsPosition
-     * within parent's app bounds to balance the visual appearance. The policy of aspect ratio has
-     * higher priority than the requested override bounds.
-     */
-    private void resolveAspectRatioRestriction(Configuration newParentConfiguration) {
-        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
-        final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride;
-        final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
-        final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
-        // Use tmp bounds to calculate aspect ratio so we can know whether the activity should use
-        // restricted size (resolved bounds may be the requested override bounds).
-        mTmpBounds.setEmpty();
-        final AppCompatAspectRatioPolicy aspectRatioPolicy = mAppCompatController
-                .getAspectRatioPolicy();
-        aspectRatioPolicy.applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds);
-        // If the out bounds is not empty, it means the activity cannot fill parent's app bounds,
-        // then they should be aligned later in #updateResolvedBoundsPosition()
-        if (!mTmpBounds.isEmpty()) {
-            resolvedBounds.set(mTmpBounds);
-        }
-        if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
-            // Compute the configuration based on the resolved bounds. If aspect ratio doesn't
-            // restrict, the bounds should be the requested override bounds.
-            mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo();
-            computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
-            aspectRatioPolicy.setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds));
-        }
-    }
-
     @Override
     public Rect getBounds() {
         // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities
@@ -9443,11 +9240,6 @@
                 mLastReportedConfiguration);
 
         if (shouldRelaunchLocked(changes, mTmpConfig)) {
-            // Aha, the activity isn't handling the change, so DIE DIE DIE.
-            if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
-                    && !mTransitionController.isShellTransitionsEnabled()) {
-                startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes);
-            }
             final boolean displayMayChange = mTmpConfig.windowConfiguration.getDisplayRotation()
                     != getWindowConfiguration().getDisplayRotation()
                     || !mTmpConfig.windowConfiguration.getMaxBounds().equals(
@@ -9455,10 +9247,8 @@
             final boolean isAppResizeOnly = !displayMayChange
                     && (changes & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
                             | CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)) == 0;
-            // Do not preserve window if it is freezing screen because the original window won't be
-            // able to update drawn state that causes freeze timeout.
             // TODO(b/258618073): Always preserve if possible.
-            final boolean preserveWindow = isAppResizeOnly && !mFreezingScreen;
+            final boolean preserveWindow = isAppResizeOnly;
             final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
             if (hasResizeChange) {
                 final boolean isDragResizing = task.isDragResizing();
@@ -9754,7 +9544,6 @@
                 scheduleStopForRestartProcess();
             });
         } else {
-            startFreezingScreen();
             scheduleStopForRestartProcess();
         }
     }
@@ -10141,7 +9930,7 @@
         proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
         proto.write(SHOULD_SEND_COMPAT_FAKE_FOCUS, shouldSendCompatFakeFocus());
         final AppCompatCameraOverrides cameraOverrides =
-                mAppCompatController.getAppCompatCameraOverrides();
+                mAppCompatController.getCameraOverrides();
         proto.write(SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT,
                 cameraOverrides.shouldForceRotateForCameraCompat());
         proto.write(SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT,
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index 597f75a..25e38b3 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -77,10 +77,10 @@
         final boolean cycleThroughStop =
                 mWmService.mAppCompatConfiguration
                         .isCameraCompatRefreshCycleThroughStopEnabled()
-                        && !activity.mAppCompatController.getAppCompatCameraOverrides()
+                        && !activity.mAppCompatController.getCameraOverrides()
                             .shouldRefreshActivityViaPauseForCameraCompat();
 
-        activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(true);
+        activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(true);
         ProtoLog.v(WM_DEBUG_STATES,
                 "Refreshing activity for freeform camera compatibility treatment, "
                         + "activityRecord=%s", activity);
@@ -97,25 +97,25 @@
                 }
             }, REFRESH_CALLBACK_TIMEOUT_MS);
         } catch (RemoteException e) {
-            activity.mAppCompatController.getAppCompatCameraOverrides()
+            activity.mAppCompatController.getCameraOverrides()
                     .setIsRefreshRequested(false);
         }
     }
 
     boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
-        return activity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
+        return activity.mAppCompatController.getCameraOverrides().isRefreshRequested();
     }
 
     void onActivityRefreshed(@NonNull ActivityRecord activity) {
         // TODO(b/333060789): can we tell that refresh did not happen by observing the activity
         //  state?
-        activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(false);
+        activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(false);
     }
 
     private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
             @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
         return mWmService.mAppCompatConfiguration.isCameraCompatRefreshEnabled()
-                && activity.mAppCompatController.getAppCompatCameraOverrides()
+                && activity.mAppCompatController.getCameraOverrides()
                     .shouldRefreshActivityForCameraCompat()
                 && ArrayUtils.find(mEvaluators.toArray(), evaluator ->
                 ((Evaluator) evaluator)
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bdbd0d1..8e4d4be 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -124,6 +124,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.OperationCanceledException;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -3246,11 +3247,14 @@
     private void sendCanNotEmbedActivityError(TaskFragment taskFragment,
             @EmbeddingCheckResult int result) {
         final String errMsg;
-        switch(result) {
+        boolean fatalError = true;
+        switch (result) {
             case EMBEDDING_DISALLOWED_NEW_TASK: {
                 errMsg = "Cannot embed " + mStartActivity + " that launched on another task"
                         + ",mLaunchMode=" + launchModeToString(mLaunchMode)
                         + ",mLaunchFlag=" + Integer.toHexString(mLaunchFlags);
+                // This is a known possible scenario, which should not be a fatal error.
+                fatalError = false;
                 break;
             }
             case EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION: {
@@ -3270,7 +3274,8 @@
             mService.mWindowOrganizerController.sendTaskFragmentOperationFailure(
                     taskFragment.getTaskFragmentOrganizer(), mRequest.errorCallbackToken,
                     taskFragment, OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT,
-                    new SecurityException(errMsg));
+                    fatalError ? new SecurityException(errMsg)
+                            : new OperationCanceledException(errMsg));
         } else {
             // If the taskFragment is not organized, just dump error message as warning logs.
             Slog.w(TAG, errMsg);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7321f28..d4f9c090 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -509,7 +509,6 @@
         int changes;
         // If the activity was relaunched to match the new configuration.
         boolean activityRelaunched;
-        boolean mIsUpdating;
     }
 
     /** Current sequencing integer of the configuration, for skipping old configurations. */
@@ -4694,14 +4693,12 @@
             if (values != null) {
                 changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);
                 mTmpUpdateConfigurationResult.changes = changes;
-                mTmpUpdateConfigurationResult.mIsUpdating = true;
             }
 
             if (!deferResume) {
                 kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
             }
         } finally {
-            mTmpUpdateConfigurationResult.mIsUpdating = false;
             continueWindowLayout();
         }
         mTmpUpdateConfigurationResult.activityRelaunched = !kept;
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 086b11c..fa04955 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -272,7 +272,7 @@
         final boolean isLandscape = isFixedOrientationLandscape(
                 mActivityRecord.getOverrideOrientation());
         final AppCompatCameraOverrides cameraOverrides =
-                mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
+                mActivityRecord.mAppCompatController.getCameraOverrides();
         // Don't resize to split screen size when in book mode if letterbox position is centered
         return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
                 || cameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 4ecd0be..ab1778a 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -56,6 +56,8 @@
     @NonNull
     private final AppCompatAspectRatioState mAppCompatAspectRatioState;
 
+    private final Rect mTmpBounds = new Rect();
+
     AppCompatAspectRatioPolicy(@NonNull ActivityRecord activityRecord,
             @NonNull TransparentPolicy transparentPolicy,
             @NonNull AppCompatOverrides appCompatOverrides) {
@@ -222,6 +224,45 @@
         return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
     }
 
+    /**
+     * Resolves aspect ratio restrictions for an activity. If the bounds are restricted by
+     * aspect ratio, the position will be adjusted later in {@link #updateResolvedBoundsPosition}
+     * within parent's app bounds to balance the visual appearance. The policy of aspect ratio has
+     * higher priority than the requested override bounds.
+     */
+    void resolveAspectRatioRestrictionIfNeeded(@NonNull Configuration newParentConfiguration) {
+        // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
+        // are already calculated in resolveFixedOrientationConfiguration.
+        // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
+        if (isLetterboxedForFixedOrientationAndAspectRatio()
+                || getOverrides().hasFullscreenOverride()) {
+            return;
+        }
+        final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration();
+        final Rect parentAppBounds =
+                mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride;
+        final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+        final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+        // Use tmp bounds to calculate aspect ratio so we can know whether the activity should
+        // use restricted size (resolved bounds may be the requested override bounds).
+        mTmpBounds.setEmpty();
+        applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds);
+        // If the out bounds is not empty, it means the activity cannot fill parent's app
+        // bounds, then they should be aligned later in #updateResolvedBoundsPosition().
+        if (!mTmpBounds.isEmpty()) {
+            resolvedBounds.set(mTmpBounds);
+        }
+        if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
+            // Compute the configuration based on the resolved bounds. If aspect ratio doesn't
+            // restrict, the bounds should be the requested override bounds.
+            // TODO(b/384473893): Improve ActivityRecord usage here.
+            mActivityRecord.mResolveConfigHint.mTmpOverrideDisplayInfo =
+                    mActivityRecord.getFixedRotationTransformDisplayInfo();
+            mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
+            setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds));
+        }
+    }
+
     private boolean isParentFullscreenPortrait() {
         final WindowContainer<?> parent = mActivityRecord.getParent();
         return parent != null
@@ -364,6 +405,11 @@
                 && !dc.getIgnoreOrientationRequest();
     }
 
+    @NonNull
+    private AppCompatAspectRatioOverrides getOverrides() {
+        return mActivityRecord.mAppCompatController.getAspectRatioOverrides();
+    }
+
     private static class AppCompatAspectRatioState {
         // Whether the aspect ratio restrictions applied to the activity bounds
         // in applyAspectRatio().
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index 6074608..276c7d2 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -277,7 +277,7 @@
      */
     static boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
         return AppCompatCameraPolicy.isCameraRunningAndWindowingModeEligible(activityRecord)
-                && activityRecord.mAppCompatController.getAppCompatCameraOverrides()
+                && activityRecord.mAppCompatController.getCameraOverrides()
                         .isOverrideMinAspectRatioForCameraEnabled();
     }
 }
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index cc9cd90..b7d8aff 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -94,8 +94,8 @@
     }
 
     @NonNull
-    AppCompatCameraOverrides getAppCompatCameraOverrides() {
-        return mAppCompatOverrides.getAppCompatCameraOverrides();
+    AppCompatCameraOverrides getCameraOverrides() {
+        return mAppCompatOverrides.getCameraOverrides();
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index 4494586..6a8040a 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -28,6 +31,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.res.Configuration.Orientation;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
@@ -55,6 +59,11 @@
 
     private boolean mLastShouldShowLetterboxUi;
 
+    // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
+    // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
+    // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
+    private boolean mIsEligibleForFixedOrientationLetterbox;
+
     AppCompatLetterboxPolicy(@NonNull ActivityRecord  activityRecord,
             @NonNull AppCompatConfiguration appCompatConfiguration) {
         mActivityRecord = activityRecord;
@@ -66,6 +75,10 @@
         mAppCompatConfiguration = appCompatConfiguration;
     }
 
+    void resetFixedOrientationLetterboxEligibility() {
+        mIsEligibleForFixedOrientationLetterbox = false;
+    }
+
     /** Cleans up {@link Letterbox} if it exists.*/
     void stop() {
         mLetterboxPolicyState.stop();
@@ -91,6 +104,43 @@
         mLetterboxPolicyState.getLetterboxInnerBounds(outBounds);
     }
 
+    /**
+     * Checks if the current activity is eligible to be letterboxed because of a fixed orientation.
+     *
+     * @param forcedOrientation The requeste orientation
+     * @param parentOrientation The orientation of the parent container.
+     * @return {@code true} if the activity can be letterboxed because of the requested fixed
+     * orientation.
+     */
+    boolean resolveFixedOrientationLetterboxEligibility(@Orientation int forcedOrientation,
+            @Orientation int parentOrientation) {
+        mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED
+                && forcedOrientation != parentOrientation;
+        return mIsEligibleForFixedOrientationLetterbox;
+    }
+
+    /**
+     * Whether this activity is eligible for letterbox eduction.
+     *
+     * <p>Conditions that need to be met:
+     *
+     * <ul>
+     *     <li>{@link AppCompatConfiguration#getIsEducationEnabled} is true.
+     *     <li>The activity is eligible for fixed orientation letterbox.
+     *     <li>The activity is in fullscreen.
+     *     <li>The activity is portrait-only.
+     *     <li>The activity doesn't have a starting window (education should only be displayed
+     *     once the starting window is removed in {@link #removeStartingWindow}).
+     * </ul>
+     */
+    boolean isEligibleForLetterboxEducation() {
+        return mAppCompatConfiguration.getIsEducationEnabled()
+                && mIsEligibleForFixedOrientationLetterbox
+                && mActivityRecord.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT
+                && mActivityRecord.mStartingWindow == null;
+    }
+
     @Nullable
     LetterboxDetails getLetterboxDetails() {
         final WindowState w = mActivityRecord.findMainWindow();
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 6202f80..35fa39d 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -100,7 +100,7 @@
         }
 
         if (displayContent != null
-                && mAppCompatOverrides.getAppCompatCameraOverrides()
+                && mAppCompatOverrides.getCameraOverrides()
                     .isOverrideOrientationOnlyForCameraEnabled()
                 && !AppCompatCameraPolicy
                     .isActivityEligibleForOrientationOverride(mActivityRecord)) {
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 2d0ff9b..811a39c 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -29,7 +29,7 @@
     @NonNull
     private final AppCompatOrientationOverrides mOrientationOverrides;
     @NonNull
-    private final AppCompatCameraOverrides mAppCompatCameraOverrides;
+    private final AppCompatCameraOverrides mCameraOverrides;
     @NonNull
     private final AppCompatAspectRatioOverrides mAspectRatioOverrides;
     @NonNull
@@ -46,10 +46,10 @@
             @NonNull AppCompatConfiguration appCompatConfiguration,
             @NonNull OptPropFactory optPropBuilder,
             @NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) {
-        mAppCompatCameraOverrides = new AppCompatCameraOverrides(activityRecord,
+        mCameraOverrides = new AppCompatCameraOverrides(activityRecord,
                 appCompatConfiguration, optPropBuilder);
         mOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
-                appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
+                appCompatConfiguration, optPropBuilder, mCameraOverrides);
         mReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
                 appCompatConfiguration, appCompatDeviceStateQuery);
         mAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
@@ -69,8 +69,8 @@
     }
 
     @NonNull
-    AppCompatCameraOverrides getAppCompatCameraOverrides() {
-        return mAppCompatCameraOverrides;
+    AppCompatCameraOverrides getCameraOverrides() {
+        return mCameraOverrides;
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 1ab0868..67f5b9b 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -150,10 +150,12 @@
             appCompatTaskInfo.setTopActivityInSizeCompat(top.fillsParent());
         }
         // Whether the direct top activity is eligible for letterbox education.
-        appCompatTaskInfo.setEligibleForLetterboxEducation(
-                isTopActivityResumed && top.isEligibleForLetterboxEducation());
-        appCompatTaskInfo.setLetterboxEducationEnabled(top.mAppCompatController
-                .getAppCompatLetterboxOverrides().isLetterboxEducationEnabled());
+        appCompatTaskInfo.setEligibleForLetterboxEducation(isTopActivityResumed
+                && top.mAppCompatController.getAppCompatLetterboxPolicy()
+                    .isEligibleForLetterboxEducation());
+        appCompatTaskInfo.setLetterboxEducationEnabled(
+                top.mAppCompatController.getAppCompatLetterboxOverrides()
+                        .isLetterboxEducationEnabled());
 
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
                 top.mAppCompatController.getAspectRatioOverrides();
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index a972ecb..0a2f685 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1195,27 +1195,12 @@
     private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
             ArrayMap<WindowContainer, Integer> outReasons) {
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                "Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(),
-                mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout());
+                "Checking %d opening apps (timeout=%b)...", apps.size(),
+                mDisplayContent.mAppTransition.isTimeout());
         if (mDisplayContent.mAppTransition.isTimeout()) {
             return true;
         }
-        final ScreenRotationAnimation screenRotationAnimation = mService.mRoot.getDisplayContent(
-                Display.DEFAULT_DISPLAY).getRotationAnimation();
 
-        // Imagine the case where we are changing orientation due to an app transition, but a
-        // previous orientation change is still in progress. We won't process the orientation
-        // change for our transition because we need to wait for the rotation animation to
-        // finish.
-        // If we start the app transition at this point, we will interrupt it halfway with a
-        // new rotation animation after the old one finally finishes. It's better to defer the
-        // app transition.
-        if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()
-                && mDisplayContent.getDisplayRotation().needsUpdate()) {
-            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                    "Delaying app transition for screen rotation animation to finish");
-            return false;
-        }
         for (int i = 0; i < apps.size(); i++) {
             WindowContainer wc = apps.valueAt(i);
             final ActivityRecord activity = getAppFromContainer(wc);
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index 601b17c..576e5d5 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -51,6 +51,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
+import android.view.ContextThemeWrapper;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
@@ -498,10 +499,21 @@
             }
         }
         if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_PAGE_SIZE_MISMATCH)) {
+            Context context =  getUiContextForActivity(ar);
+            // PageSizeMismatchDialog has link in message which should open in browser.
+            // Starting activity from non-activity context is not allowed and flag
+            // FLAG_ACTIVITY_NEW_TASK is needed to start activity.
+            context =  new ContextThemeWrapper(context, context.getThemeResId()) {
+                @Override
+                public void startActivity(Intent intent) {
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    super.startActivity(intent);
+                }
+            };
             pageSizeMismatchDialog =
                     new PageSizeMismatchDialog(
                             AppWarnings.this,
-                            getUiContextForActivity(ar),
+                            context,
                             ar.info.applicationInfo,
                             userId,
                             warning);
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 6b6f011..d3fd0e3 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -135,8 +135,7 @@
         // decides not to perform seamless rotation, it only affects whether to use fade animation
         // when the windows are drawn. If the windows are not too slow (after rotation animation is
         // done) to be drawn, the visual result can still look smooth.
-        mHasScreenRotationAnimation =
-                displayContent.getRotationAnimation() != null || mTransitionOp == OP_CHANGE;
+        mHasScreenRotationAnimation = mTransitionOp == OP_CHANGE;
         if (mHasScreenRotationAnimation) {
             // Hide the windows immediately because screen should have been covered by screenshot.
             mHideImmediately = true;
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index b9febb83..119709e 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -1036,8 +1036,10 @@
 
         // Normal apps with visible app window will be allowed to start activity if app switching
         // is allowed, or apps like live wallpaper with non app visible window will be allowed.
+        // The home app can start apps even if app switches are usually disallowed.
         final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
-                || state.mAppSwitchState == APP_SWITCH_FG_ONLY;
+                || 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");
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index f5bc9f0..230cd33 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -221,7 +221,7 @@
     }
 
     boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity) {
-        return  activity.mAppCompatController.getAppCompatCameraOverrides()
+        return  activity.mAppCompatController.getCameraOverrides()
                 .shouldApplyFreeformTreatmentForCameraCompat()
                 && activity.inFreeformWindowingMode()
                 && mCameraStateMonitor.isCameraRunningForActivity(activity);
@@ -232,7 +232,7 @@
         // different camera compat aspect ratio set: this allows per-app camera compat override
         // aspect ratio to be smaller than the default.
         return isInFreeformCameraCompatMode(activity) && !activity.mAppCompatController
-                .getAppCompatCameraOverrides().isOverrideMinAspectRatioForCameraEnabled();
+                .getCameraOverrides().isOverrideMinAspectRatioForCameraEnabled();
     }
 
     boolean isInFreeformCameraCompatMode(@NonNull ActivityRecord activity) {
@@ -307,7 +307,7 @@
     boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity,
             boolean checkOrientation) {
         int orientation = activity.getRequestedConfigurationOrientation();
-        return activity.mAppCompatController.getAppCompatCameraOverrides()
+        return activity.mAppCompatController.getCameraOverrides()
                 .shouldApplyFreeformTreatmentForCameraCompat()
                 && mCameraStateMonitor.isCameraRunningForActivity(activity)
                 && (!checkOrientation || orientation != ORIENTATION_UNDEFINED)
@@ -333,6 +333,6 @@
                 || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
             return false;
         }
-        return topActivity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
+        return topActivity.mAppCompatController.getCameraOverrides().isRefreshRequested();
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5435d8f..60c80a3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -128,7 +128,6 @@
 import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
 import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
 import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
-import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
 import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS;
 import static com.android.server.wm.EventLogTags.IMF_REMOVE_IME_SCREENSHOT;
 import static com.android.server.wm.EventLogTags.IMF_SHOW_IME_SCREENSHOT;
@@ -150,7 +149,6 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
 import static com.android.server.wm.WindowManagerService.dipToPixel;
 import static com.android.server.wm.WindowState.EXCLUSION_LEFT;
 import static com.android.server.wm.WindowState.EXCLUSION_RIGHT;
@@ -630,8 +628,6 @@
     /** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
     final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>();
 
-    private ScreenRotationAnimation mScreenRotationAnimation;
-
     /**
      * Sequence number for the current layout pass.
      */
@@ -1594,8 +1590,6 @@
                     mTransitionController.setDisplaySyncMethod(startBounds, endBounds, this);
                     collectDisplayChange(transition);
                 }
-            } else if (mLastHasContent) {
-                mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
             }
             sendNewConfiguration();
         }
@@ -1638,7 +1632,6 @@
         // DisplayContent#updateRotationUnchecked.
         if (mWaitingForConfig) {
             mWaitingForConfig = false;
-            mWmService.mLastFinishedFreezeSource = "config-unchanged";
             setLayoutNeeded();
             mWmService.mWindowPlacerLocked.performSurfacePlacement();
         }
@@ -1647,8 +1640,7 @@
 
     @Override
     boolean onDescendantOrientationChanged(@Nullable WindowContainer requestingContainer) {
-        final Configuration config = updateOrientation(
-                requestingContainer, false /* forceUpdate */);
+        final Configuration config = updateOrientationAndComputeConfig(false /* forceUpdate */);
         // If display rotation class tells us that it doesn't consider app requested orientation,
         // this display won't rotate just because of an app changes its requested orientation. Thus
         // it indicates that this display chooses not to handle this request.
@@ -1696,28 +1688,17 @@
     /**
      * Update orientation of the display, returning a non-null new Configuration if it has
      * changed from the current orientation. If a non-null configuration is returned, someone must
-     * call {@link WindowManagerService#setNewDisplayOverrideConfiguration(Configuration,
-     * DisplayContent)} to tell the window manager it can unfreeze the screen. This will typically
-     * be done by calling {@link #sendNewConfiguration}.
+     * request a change transition or call {@link #sendNewConfiguration}.
      *
-     * @param freezeDisplayWindow Freeze the app window if the orientation is changed.
      * @param forceUpdate See {@link DisplayRotation#updateRotationUnchecked(boolean)}
      */
-    Configuration updateOrientation(WindowContainer<?> freezeDisplayWindow, boolean forceUpdate) {
+    Configuration updateOrientationAndComputeConfig(boolean forceUpdate) {
         if (!mDisplayReady) {
             return null;
         }
 
         Configuration config = null;
         if (updateOrientation(forceUpdate)) {
-            // If we changed the orientation but mOrientationChangeComplete is already true,
-            // we used seamless rotation, and we don't need to freeze the screen.
-            if (freezeDisplayWindow != null && !mWmService.mRoot.mOrientationChangeComplete) {
-                final ActivityRecord activity = freezeDisplayWindow.asActivityRecord();
-                if (activity != null && activity.mayFreezeScreenLocked()) {
-                    activity.startFreezingScreen();
-                }
-            }
             config = new Configuration();
             computeScreenConfiguration(config);
         }
@@ -2254,8 +2235,6 @@
                 mDisplayRotation.isRotatingSeamlessly() && !shellTransitions;
         final Transaction transaction =
                 shellTransitions ? getSyncTransaction() : getPendingTransaction();
-        ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
-                ? null : getRotationAnimation();
         // We need to update our screen size information to match the new rotation. If the rotation
         // has actually changed then this method will return true and, according to the comment at
         // the top of the method, the caller is obligated to call computeNewConfigurationLocked().
@@ -2263,12 +2242,6 @@
         // #computeScreenConfiguration() later.
         updateDisplayAndOrientation(null /* outConfig */);
 
-        // NOTE: We disable the rotation in the emulator because
-        //       it doesn't support hardware OpenGL emulation yet.
-        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
-            screenRotationAnimation.setRotation(transaction, rotation);
-        }
-
         if (!shellTransitions) {
             forAllWindows(w -> {
                 w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
@@ -2932,20 +2905,6 @@
     @ScreenOrientation
     @Override
     int getOrientation() {
-        if (mWmService.mDisplayFrozen) {
-            if (mWmService.mPolicy.isKeyguardLocked()) {
-                // Use the last orientation the while the display is frozen with the keyguard
-                // locked. This could be the keyguard forced orientation or from a SHOW_WHEN_LOCKED
-                // window. We don't want to check the show when locked window directly though as
-                // things aren't stable while the display is frozen, for example the window could be
-                // momentarily unavailable due to activity relaunch.
-                ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "Display id=%d is frozen while keyguard locked, return %d",
-                        mDisplayId, getLastOrientation());
-                return getLastOrientation();
-            }
-        }
-
         final int compatOrientation = mAppCompatCameraPolicy.getOrientation();
         if (compatOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
             mLastOrientationSource = null;
@@ -3413,12 +3372,10 @@
             mAppTransition.removeAppTransitionTimeoutCallbacks();
             mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
             handleAnimatingStoppedAndTransition();
-            mWmService.stopFreezingDisplayLocked();
             mDeviceStateController.unregisterDeviceStateCallback(mDeviceStateConsumer);
             super.removeImmediately();
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
             mPointerEventDispatcher.dispose();
-            setRotationAnimation(null);
             // Unlink death from remote to clear the reference from binder -> mRemoteInsetsDeath
             // -> this DisplayContent.
             setRemoteInsetsController(null);
@@ -3514,24 +3471,6 @@
         RotationUtils.rotateBounds(inOutBounds, mTmpRect, oldRotation, newRotation);
     }
 
-    public void setRotationAnimation(ScreenRotationAnimation screenRotationAnimation) {
-        final ScreenRotationAnimation prev = mScreenRotationAnimation;
-        mScreenRotationAnimation = screenRotationAnimation;
-        if (prev != null) {
-            prev.kill();
-        }
-
-        // Hide the windows which are not significant in rotation animation. So that the windows
-        // don't need to block the unfreeze time.
-        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
-            startAsyncRotationIfNeeded();
-        }
-    }
-
-    public ScreenRotationAnimation getRotationAnimation() {
-        return mScreenRotationAnimation;
-    }
-
     /**
      * Collects this display into an already-collecting transition.
      */
@@ -3595,12 +3534,6 @@
         }
     }
 
-    /** If the display is in transition, there should be a screenshot covering it. */
-    @Override
-    boolean inTransition() {
-        return mScreenRotationAnimation != null || super.inTransition();
-    }
-
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
             @WindowTracingLogLevel int logLevel) {
@@ -3616,10 +3549,6 @@
         proto.write(DPI, mBaseDisplayDensity);
         mDisplayInfo.dumpDebug(proto, DISPLAY_INFO);
         mDisplayRotation.dumpDebug(proto, DISPLAY_ROTATION);
-        final ScreenRotationAnimation screenRotationAnimation = getRotationAnimation();
-        if (screenRotationAnimation != null) {
-            screenRotationAnimation.dumpDebug(proto, SCREEN_ROTATION_ANIMATION);
-        }
         mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
         proto.write(MIN_SIZE_OF_RESIZEABLE_TASK_DP, mMinSizeOfResizeableTaskDp);
         if (mTransitionController.isShellTransitionsEnabled()) {
@@ -3766,16 +3695,6 @@
 
         pw.println();
 
-        final ScreenRotationAnimation rotationAnimation = getRotationAnimation();
-        if (rotationAnimation != null) {
-            pw.println("  mScreenRotationAnimation:");
-            rotationAnimation.printTo(subPrefix, pw);
-        } else if (dumpAll) {
-            pw.println("  no ScreenRotationAnimation ");
-        }
-
-        pw.println();
-
         // Dump root task references
         final Task rootHomeTask = getDefaultTaskDisplayArea().getRootHomeTask();
         if (rootHomeTask != null) {
@@ -5068,13 +4987,6 @@
         return win != null;
     }
 
-    void onWindowFreezeTimeout() {
-        Slog.w(TAG_WM, "Window freeze timeout expired.");
-        mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
-
-        mWmService.mWindowPlacerLocked.performSurfacePlacement();
-    }
-
     /**
      * Callbacks when the given type of {@link WindowContainer} animation finished running in the
      * hierarchy.
@@ -5297,8 +5209,7 @@
 
     boolean okToDisplay(boolean ignoreFrozen, boolean ignoreScreenOn) {
         if (mDisplayId == DEFAULT_DISPLAY) {
-            return (!mWmService.mDisplayFrozen || ignoreFrozen)
-                    && mWmService.mDisplayEnabled
+            return mWmService.mDisplayEnabled
                     && (ignoreScreenOn || mWmService.mPolicy.isScreenOn());
         }
         return mDisplayInfo.state == Display.STATE_ON;
@@ -5443,10 +5354,7 @@
             // We skip IME windows so they're processed just above their target.
             // Note that this method check should align with {@link
             // WindowState#applyImeWindowsIfNeeded} in case of any state mismatch.
-            return dc.mImeLayeringTarget != null
-                    // Make sure that the IME window won't be skipped to report that it has
-                    // completed the orientation change.
-                    && !dc.mWmService.mDisplayFrozen;
+            return dc.mImeLayeringTarget != null;
         }
 
         /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */
@@ -6427,14 +6335,12 @@
                     changes = performDisplayOverrideConfigUpdate(values);
                 }
                 mAtmService.mTmpUpdateConfigurationResult.changes = changes;
-                mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = true;
             }
 
             if (!deferResume) {
                 kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
             }
         } finally {
-            mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = false;
             mAtmService.continueWindowLayout();
         }
 
@@ -6490,7 +6396,6 @@
         mCurrentOverrideConfigurationChanges = 0;
         if (mWaitingForConfig) {
             mWaitingForConfig = false;
-            mWmService.mLastFinishedFreezeSource = "new-config";
         }
         mAtmService.addWindowLayoutReasons(
                 ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
@@ -7086,13 +6991,6 @@
         }
 
         @Override
-        public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
-            // It is only needed when freezing display in legacy transition.
-            if (mTransitionController.isShellTransitionsEnabled()) return;
-            continueUpdateOrientationForDiffOrienLaunchingApp();
-        }
-
-        @Override
         public void onAppTransitionTimeoutLocked() {
             continueUpdateOrientationForDiffOrienLaunchingApp();
         }
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index f53bc70..f8c1755 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -22,12 +22,8 @@
 import static android.view.Display.TYPE_EXTERNAL;
 import static android.view.Display.TYPE_OVERLAY;
 import static android.view.Display.TYPE_VIRTUAL;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
 import static com.android.server.wm.DisplayRotationProto.FIXED_TO_USER_ROTATION_MODE;
@@ -41,10 +37,7 @@
 import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_NOSENSOR;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
-import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION;
 
-import android.annotation.AnimRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -104,13 +97,6 @@
 
     private static final int ROTATION_UNDEFINED = -1;
 
-    private static class RotationAnimationPair {
-        @AnimRes
-        int mEnter;
-        @AnimRes
-        int mExit;
-    }
-
     @Nullable
     final FoldController mFoldController;
 
@@ -130,7 +116,6 @@
     private final int mCarDockRotation;
     private final int mDeskDockRotation;
     private final int mUndockedHdmiRotation;
-    private final RotationAnimationPair mTmpRotationAnim = new RotationAnimationPair();
     private final RotationHistory mRotationHistory = new RotationHistory();
     private final RotationLockHistory mRotationLockHistory = new RotationLockHistory();
 
@@ -540,14 +525,6 @@
                 ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, animation in progress.");
                 return false;
             }
-            if (mService.mDisplayFrozen) {
-                // Even if the screen rotation animation has finished (e.g. isAnimating returns
-                // false), there is still some time where we haven't yet unfrozen the display. We
-                // also need to abort rotation here.
-                ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "Deferring rotation, still finishing previous rotation");
-                return false;
-            }
 
             if (mDisplayContent.mFixedRotationTransitionListener.shouldDeferRotation()) {
                 // Makes sure that after the transition is finished, updateOrientation() can see
@@ -644,17 +621,13 @@
             return true;
         }
 
-        mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
-        mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
-                mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
-
         if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
             // The screen rotation animation uses a screenshot to freeze the screen while windows
             // resize underneath. When we are rotating seamlessly, we allow the elements to
             // transition to their rotated state independently and without a freeze required.
             prepareSeamlessRotation();
         } else {
-            prepareNormalRotationAnimation();
+            cancelSeamlessRotation();
         }
 
         // Give a remote handler (system ui) some time to reposition things.
@@ -695,12 +668,6 @@
         }
     }
 
-    void prepareNormalRotationAnimation() {
-        cancelSeamlessRotation();
-        final RotationAnimationPair anim = selectRotationAnimation();
-        mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
-    }
-
     /**
      * This ensures that normal rotation animation is used. E.g. {@link #mRotatingSeamlessly} was
      * set by previous {@link #updateRotationUnchecked}, but another orientation change happens
@@ -820,79 +787,6 @@
         }
     }
 
-    /**
-     * Returns the animation to run for a rotation transition based on the top fullscreen windows
-     * {@link android.view.WindowManager.LayoutParams#rotationAnimation} and whether it is currently
-     * fullscreen and frontmost.
-     */
-    private RotationAnimationPair selectRotationAnimation() {
-        // If the screen is off or non-interactive, force a jumpcut.
-        final boolean forceJumpcut = !mDisplayPolicy.isScreenOnFully()
-                || !mService.mPolicy.okToAnimate(false /* ignoreScreenOn */);
-        final WindowState topFullscreen = mDisplayPolicy.getTopFullscreenOpaqueWindow();
-        ProtoLog.i(WM_DEBUG_ANIM, "selectRotationAnimation topFullscreen=%s"
-                + " rotationAnimation=%d forceJumpcut=%b",
-                topFullscreen,
-                topFullscreen == null ? 0 : topFullscreen.getAttrs().rotationAnimation,
-                forceJumpcut);
-        if (forceJumpcut) {
-            mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
-            mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
-            return mTmpRotationAnim;
-        }
-        if (topFullscreen != null) {
-            int animationHint = topFullscreen.getRotationAnimationHint();
-            if (animationHint < 0 && mDisplayPolicy.isTopLayoutFullscreen()) {
-                animationHint = topFullscreen.getAttrs().rotationAnimation;
-            }
-            switch (animationHint) {
-                case ROTATION_ANIMATION_CROSSFADE:
-                case ROTATION_ANIMATION_SEAMLESS: // Crossfade is fallback for seamless.
-                    mTmpRotationAnim.mExit = R.anim.rotation_animation_xfade_exit;
-                    mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
-                    break;
-                case ROTATION_ANIMATION_JUMPCUT:
-                    mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
-                    mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
-                    break;
-                case ROTATION_ANIMATION_ROTATE:
-                default:
-                    mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
-                    break;
-            }
-        } else {
-            mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
-        }
-        return mTmpRotationAnim;
-    }
-
-    /**
-     * Validate whether the current top fullscreen has specified the same
-     * {@link android.view.WindowManager.LayoutParams#rotationAnimation} value as that being passed
-     * in from the previous top fullscreen window.
-     *
-     * @param exitAnimId exiting resource id from the previous window.
-     * @param enterAnimId entering resource id from the previous window.
-     * @param forceDefault For rotation animations only, if true ignore the animation values and
-     *                     just return false.
-     * @return {@code true} if the previous values are still valid, false if they should be replaced
-     *         with the default.
-     */
-    boolean validateRotationAnimation(int exitAnimId, int enterAnimId, boolean forceDefault) {
-        switch (exitAnimId) {
-            case R.anim.rotation_animation_xfade_exit:
-            case R.anim.rotation_animation_jump_exit:
-                // These are the only cases that matter.
-                if (forceDefault) {
-                    return false;
-                }
-                final RotationAnimationPair anim = selectRotationAnimation();
-                return exitAnimId == anim.mExit && enterAnimId == anim.mEnter;
-            default:
-                return true;
-        }
-    }
-
     void restoreSettings(int userRotationMode, int userRotation, int fixedToUserRotation) {
         mFixedToUserRotation = fixedToUserRotation;
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 3c199db..dceacc3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -253,10 +253,10 @@
                 != lastReportedConfig.windowConfiguration.getDisplayRotation());
         return isTreatmentEnabledForDisplay()
                 && isTreatmentEnabledForActivity(activity)
-                && activity.mAppCompatController.getAppCompatCameraOverrides()
+                && activity.mAppCompatController.getCameraOverrides()
                     .shouldRefreshActivityForCameraCompat()
                 && (displayRotationChanged
-                || activity.mAppCompatController.getAppCompatCameraOverrides()
+                || activity.mAppCompatController.getCameraOverrides()
                         .isCameraCompatSplitScreenAspectRatioAllowed());
     }
 
@@ -281,7 +281,7 @@
     boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
         return isTreatmentEnabledForDisplay()
                 && isCameraRunningAndWindowingModeEligible(activity, /* mustBeFullscreen */ true)
-                && activity.mAppCompatController.getAppCompatCameraOverrides()
+                && activity.mAppCompatController.getCameraOverrides()
                     .shouldForceRotateForCameraCompat();
     }
 
@@ -325,7 +325,7 @@
                 // handle dynamic changes so we shouldn't force rotate them.
                 && activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
                 && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED
-                && activity.mAppCompatController.getAppCompatCameraOverrides()
+                && activity.mAppCompatController.getCameraOverrides()
                     .shouldForceRotateForCameraCompat();
     }
 
@@ -457,14 +457,14 @@
     private boolean shouldRecomputeConfigurationForCameraCompat(
             @NonNull ActivityRecord activityRecord) {
         final AppCompatCameraOverrides overrides = activityRecord.mAppCompatController
-                .getAppCompatCameraOverrides();
+                .getCameraOverrides();
         return overrides.isOverrideOrientationOnlyForCameraEnabled()
                 || overrides.isCameraCompatSplitScreenAspectRatioAllowed()
                 || shouldOverrideMinAspectRatio(activityRecord);
     }
 
     private boolean shouldOverrideMinAspectRatio(@NonNull ActivityRecord activityRecord) {
-        return activityRecord.mAppCompatController.getAppCompatCameraOverrides()
+        return activityRecord.mAppCompatController.getCameraOverrides()
                 .isOverrideMinAspectRatioForCameraEnabled()
                         && isCameraRunningAndWindowingModeEligible(activityRecord,
                                 /* mustBeFullscreen= */ true);
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 5b0cc9a..c418349 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -51,8 +51,8 @@
 import android.window.IUnhandledDragCallback;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.hidden_from_bootclasspath.com.android.window.flags.Flags;
 import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
+import com.android.window.flags.Flags;
 
 import java.util.Objects;
 import java.util.Random;
@@ -121,13 +121,6 @@
     }
 
     @VisibleForTesting
-    void cleanupListeners() {
-        if (Flags.enableConnectedDisplaysDnd()) {
-            mService.mDisplayManager.unregisterTopologyListener(mDisplayTopologyListener);
-        }
-    }
-
-    @VisibleForTesting
     Handler getHandler() {
         return mHandler;
     }
@@ -498,7 +491,8 @@
         }
     }
 
-    private void handleDisplayTopologyChange(DisplayTopology unused) {
+    @VisibleForTesting
+    void handleDisplayTopologyChange(DisplayTopology unused) {
         synchronized (mService.mGlobalLock) {
             if (mDragState == null) {
                 return;
diff --git a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java
index 29922f0..24235ef 100644
--- a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java
+++ b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java
@@ -24,8 +24,10 @@
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.text.Html;
+import android.text.method.LinkMovementMethod;
 import android.view.Window;
 import android.view.WindowManager;
+import android.widget.TextView;
 
 import com.android.internal.R;
 
@@ -69,6 +71,14 @@
         mDialog.create();
 
         final Window window = mDialog.getWindow();
-        window.setType(WindowManager.LayoutParams.TYPE_PHONE);
+        window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+    }
+
+    @Override
+    public void show() {
+        super.show();
+        // Make the links in dialog clickable
+        final TextView msgTxt = (TextView) mDialog.findViewById(android.R.id.message);
+        msgTxt.setMovementMethod(LinkMovementMethod.getInstance());
     }
 }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 7a3eb67..e2b5c83 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -78,11 +78,9 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.WINDOW_FREEZE_TIMEOUT;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE;
 import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
 
@@ -200,12 +198,6 @@
     private boolean mSustainedPerformanceModeEnabled = false;
     private boolean mSustainedPerformanceModeCurrent = false;
 
-    // During an orientation change, we track whether all windows have rendered
-    // at the new orientation, and this will be false from changing orientation until that occurs.
-    // For seamless rotation cases this always stays true, as the windows complete their orientation
-    // changes 1 by 1 without disturbing global state.
-    boolean mOrientationChangeComplete = true;
-
     private final Handler mHandler;
 
     private String mCloseSystemDialogsReason;
@@ -841,20 +833,6 @@
             }
         }
 
-        if (mWmService.mDisplayFrozen) {
-            ProtoLog.v(WM_DEBUG_ORIENTATION,
-                    "With display frozen, orientationChangeComplete=%b",
-                    mOrientationChangeComplete);
-        }
-        if (mOrientationChangeComplete) {
-            if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
-                mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
-                mWmService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
-                mWmService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
-            }
-            mWmService.stopFreezingDisplayLocked();
-        }
-
         // Destroy the surface of any windows that are no longer visible.
         i = mWmService.mDestroySurface.size();
         if (i > 0) {
@@ -881,13 +859,11 @@
             }
         }
 
-        if (!mWmService.mDisplayFrozen) {
-            // Post these on a handler such that we don't call into power manager service while
-            // holding the window manager lock to avoid lock contention with power manager lock.
-            mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides)
-                    .sendToTarget();
-            mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
-        }
+        // Post these on a handler such that we don't call into power manager service while
+        // holding the window manager lock to avoid lock contention with power manager lock.
+        mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides)
+                .sendToTarget();
+        mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
 
         if (mSustainedPerformanceModeCurrent != mSustainedPerformanceModeEnabled) {
             mSustainedPerformanceModeEnabled = mSustainedPerformanceModeCurrent;
@@ -902,8 +878,7 @@
         }
 
         if (!mWmService.mWaitingForDrawnCallbacks.isEmpty()
-                || (mOrientationChangeComplete && !isLayoutNeeded()
-                && !mUpdateRotation)) {
+                || (!isLayoutNeeded() && !mUpdateRotation)) {
             mWmService.checkDrawnWindowsLocked();
         }
 
@@ -991,7 +966,7 @@
     private void handleResizingWindows() {
         for (int i = mWmService.mResizingWindows.size() - 1; i >= 0; i--) {
             WindowState win = mWmService.mResizingWindows.get(i);
-            if (win.mAppFreezing || win.getDisplayContent().mWaitingForConfig) {
+            if (win.getDisplayContent().mWaitingForConfig) {
                 // Don't remove this window until rotation has completed and is not waiting for the
                 // complete configuration.
                 continue;
@@ -1092,12 +1067,6 @@
             mUpdateRotation = true;
             doRequest = true;
         }
-        if (mOrientationChangeComplete) {
-            mLastWindowFreezeSource = mWmService.mAnimator.mLastWindowFreezeSource;
-            if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
-                doRequest = true;
-            }
-        }
 
         return doRequest;
     }
@@ -1765,7 +1734,7 @@
         // Force-update the orientation from the WindowManager, since we need the true configuration
         // to send to the client now.
         final Configuration config =
-                displayContent.updateOrientation(starting, true /* forceUpdate */);
+                displayContent.updateOrientationAndComputeConfig(true /* forceUpdate */);
         // Visibilities may change so let the starting activity have a chance to report. Can't do it
         // when visibility is changed in each AppWindowToken because it may trigger wrong
         // configuration push because the visibility of some activities may not be updated yet.
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
deleted file mode 100644
index 4bd294b..0000000
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ /dev/null
@@ -1,825 +0,0 @@
-/*
- * Copyright (C) 2010 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.wm;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.util.RotationUtils.deltaRotation;
-import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.AnimationSpecProto.ROTATE;
-import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA;
-import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA;
-import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING;
-import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.utils.CoordinateTransforms.computeRotationMatrix;
-
-import android.animation.ArgbEvaluator;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.os.Trace;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.DisplayInfo;
-import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Transformation;
-import android.window.ScreenCapture;
-
-import com.android.internal.R;
-import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.ProtoLog;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-
-import java.io.PrintWriter;
-
-/**
- * This class handles the rotation animation when the device is rotated.
- *
- * <p>
- * The screen rotation animation is composed of 4 different part:
- * <ul>
- * <li> The screenshot: <p>
- *     A screenshot of the whole screen prior the change of orientation is taken to hide the
- *     element resizing below. The screenshot is then animated to rotate and cross-fade to
- *     the new orientation with the content in the new orientation.
- *
- * <li> The windows on the display: <p>y
- *      Once the device is rotated, the screen and its content are in the new orientation. The
- *      animation first rotate the new content into the old orientation to then be able to
- *      animate to the new orientation
- *
- * <li> The Background color frame: <p>
- *      To have the animation seem more seamless, we add a color transitioning background behind the
- *      exiting and entering layouts. We compute the brightness of the start and end
- *      layouts and transition from the two brightness values as grayscale underneath the animation
- *
- * <li> The entering Blackframe: <p>
- *     The enter Blackframe is similar to the exit Blackframe but is only used when a custom
- *     rotation animation is used and matches the new content size instead of the screenshot.
- * </ul>
- *
- * Each part has its own Surface which are then animated by {@link SurfaceAnimator}s.
- */
-class ScreenRotationAnimation {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "ScreenRotationAnimation" : TAG_WM;
-
-    private final Context mContext;
-    private final DisplayContent mDisplayContent;
-    private final float[] mTmpFloats = new float[9];
-    private final Transformation mRotateExitTransformation = new Transformation();
-    private final Transformation mRotateEnterTransformation = new Transformation();
-    // Complete transformations being applied.
-    private final Matrix mSnapshotInitialMatrix = new Matrix();
-    private final WindowManagerService mService;
-    /** Only used for custom animations and not screen rotation. */
-    private SurfaceControl mEnterBlackFrameLayer;
-    /** This layer contains the actual screenshot that is to be faded out. */
-    private SurfaceControl mScreenshotLayer;
-    private SurfaceControl[] mRoundedCornerOverlay;
-    /**
-     * Only used for screen rotation and not custom animations. Layered behind all other layers
-     * to avoid showing any "empty" spots
-     */
-    private SurfaceControl mBackColorSurface;
-    private BlackFrame mEnteringBlackFrame;
-
-    private final int mOriginalRotation;
-    private final int mOriginalWidth;
-    private final int mOriginalHeight;
-    private int mCurRotation;
-
-    // The current active animation to move from the old to the new rotated
-    // state.  Which animation is run here will depend on the old and new
-    // rotations.
-    private Animation mRotateExitAnimation;
-    private Animation mRotateEnterAnimation;
-    private Animation mRotateAlphaAnimation;
-    private boolean mStarted;
-    private boolean mAnimRunning;
-    private boolean mFinishAnimReady;
-    private long mFinishAnimStartTime;
-    private SurfaceRotationAnimationController mSurfaceRotationAnimationController;
-    /** Intensity of light/whiteness of the layout before rotation occurs. */
-    private float mStartLuma;
-    /** Intensity of light/whiteness of the layout after rotation occurs. */
-    private float mEndLuma;
-
-    ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {
-        mService = displayContent.mWmService;
-        mContext = mService.mContext;
-        mDisplayContent = displayContent;
-        final Rect currentBounds = displayContent.getBounds();
-        final int width = currentBounds.width();
-        final int height = currentBounds.height();
-
-        // Screenshot does NOT include rotation!
-        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        final int realOriginalRotation = displayInfo.rotation;
-
-        mOriginalRotation = originalRotation;
-        // If the delta is not zero, the rotation of display may not change, but we still want to
-        // apply rotation animation because there should be a top app shown as rotated. So the
-        // specified original rotation customizes the direction of animation to have better look
-        // when restoring the rotated app to the same rotation as current display.
-        final int delta = deltaRotation(originalRotation, realOriginalRotation);
-        final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270;
-        mOriginalWidth = flipped ? height : width;
-        mOriginalHeight = flipped ? width : height;
-        final int logicalWidth = displayInfo.logicalWidth;
-        final int logicalHeight = displayInfo.logicalHeight;
-        final boolean isSizeChanged =
-                logicalWidth > mOriginalWidth == logicalHeight > mOriginalHeight
-                && (logicalWidth != mOriginalWidth || logicalHeight != mOriginalHeight);
-        mSurfaceRotationAnimationController = new SurfaceRotationAnimationController();
-
-        // Check whether the current screen contains any secure content.
-        boolean isSecure = displayContent.hasSecureWindowOnScreen();
-        final int displayId = displayContent.getDisplayId();
-        final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
-
-        try {
-            final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer;
-            if (isSizeChanged) {
-                // Temporarily not skip screenshot for the rounded corner overlays and screenshot
-                // the whole display to include the rounded corner overlays.
-                setSkipScreenshotForRoundedCornerOverlays(false, t);
-                ScreenCapture.LayerCaptureArgs captureArgs =
-                        new ScreenCapture.LayerCaptureArgs.Builder(
-                                displayContent.getSurfaceControl())
-                                .setCaptureSecureLayers(true)
-                                .setAllowProtected(true)
-                                .setSourceCrop(new Rect(0, 0, width, height))
-                                .setHintForSeamlessTransition(true)
-                                .build();
-                screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
-            } else {
-                ScreenCapture.LayerCaptureArgs captureArgs =
-                        new ScreenCapture.LayerCaptureArgs.Builder(
-                                displayContent.getSurfaceControl())
-                                .setCaptureSecureLayers(true)
-                                .setAllowProtected(true)
-                                .setSourceCrop(new Rect(0, 0, width, height))
-                                .setHintForSeamlessTransition(true)
-                                .build();
-                screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
-            }
-
-            if (screenshotBuffer == null) {
-                Slog.w(TAG, "Unable to take screenshot of display " + displayId);
-                return;
-            }
-
-            // If the screenshot contains secure layers, we have to make sure the
-            // screenshot surface we display it in also has FLAG_SECURE so that
-            // the user can not screenshot secure layers via the screenshot surface.
-            if (screenshotBuffer.containsSecureLayers()) {
-                isSecure = true;
-            }
-
-            mBackColorSurface = displayContent.makeChildSurface(null)
-                    .setName("BackColorSurface")
-                    .setColorLayer()
-                    .setCallsite("ScreenRotationAnimation")
-                    .build();
-
-            String name = "RotationLayer";
-            mScreenshotLayer = displayContent.makeOverlay()
-                    .setName(name)
-                    .setOpaque(true)
-                    .setSecure(isSecure)
-                    .setCallsite("ScreenRotationAnimation")
-                    .setBLASTLayer()
-                    .build();
-            // This is the way to tell the input system to exclude this surface from occlusion
-            // detection since we don't have a window for it. We do this because this window is
-            // generated by the system as well as its content.
-            InputMonitor.setTrustedOverlayInputInfo(mScreenshotLayer, t, displayId, name);
-
-            mEnterBlackFrameLayer = displayContent.makeOverlay()
-                    .setName("EnterBlackFrameLayer")
-                    .setContainerLayer()
-                    .setCallsite("ScreenRotationAnimation")
-                    .build();
-
-            HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
-                    "ScreenRotationAnimation#getMedianBorderLuma");
-            mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer,
-                    screenshotBuffer.getColorSpace());
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-
-            t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
-            t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
-            // If hdr layers are on-screen, e.g. picture-in-picture mode, the screenshot of
-            // rotation animation is an sdr image containing tone-mapping hdr content, then
-            // disable dimming effect to get avoid of hdr content being dimmed during animation.
-            t.setDimmingEnabled(mScreenshotLayer, !screenshotBuffer.containsHdrLayers());
-            t.setLayer(mBackColorSurface, -1);
-            t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
-            t.setAlpha(mBackColorSurface, 1);
-            t.setBuffer(mScreenshotLayer, hardwareBuffer);
-            t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
-            t.show(mScreenshotLayer);
-            t.show(mBackColorSurface);
-            hardwareBuffer.close();
-
-            if (mRoundedCornerOverlay != null) {
-                for (SurfaceControl sc : mRoundedCornerOverlay) {
-                    if (sc.isValid()) {
-                        t.hide(sc);
-                    }
-                }
-            }
-
-        } catch (OutOfResourcesException e) {
-            Slog.w(TAG, "Unable to allocate freeze surface", e);
-        }
-
-        // If display size is changed with the same orientation, map the bounds of screenshot to
-        // the new logical display size. Currently pending transaction and RWC#mDisplayTransaction
-        // are merged to global transaction, so it can be synced with display change when calling
-        // DisplayManagerInternal#performTraversal(transaction).
-        if (mScreenshotLayer != null && isSizeChanged) {
-            displayContent.getPendingTransaction().setGeometry(mScreenshotLayer,
-                    new Rect(0, 0, mOriginalWidth, mOriginalHeight),
-                    new Rect(0, 0, logicalWidth, logicalHeight), Surface.ROTATION_0);
-        }
-
-        ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
-                "  FREEZE %s: CREATE", mScreenshotLayer);
-        if (originalRotation == realOriginalRotation) {
-            setRotation(t, realOriginalRotation);
-        } else {
-            // If the given original rotation is different from real original display rotation,
-            // this is playing non-zero degree rotation animation without display rotation change,
-            // so the snapshot doesn't need to be transformed.
-            mCurRotation = realOriginalRotation;
-            mSnapshotInitialMatrix.reset();
-            setRotationTransform(t, mSnapshotInitialMatrix);
-        }
-        t.apply();
-    }
-
-    void setSkipScreenshotForRoundedCornerOverlays(
-            boolean skipScreenshot, SurfaceControl.Transaction t) {
-        mDisplayContent.forAllWindows(w -> {
-            if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) {
-                return;
-            }
-            t.setSkipScreenshot(w.mWinAnimator.mSurfaceControl, skipScreenshot);
-        }, false);
-        if (!skipScreenshot) {
-            // Use sync apply to apply the change immediately, so that the next
-            // SC.captureDisplay can capture the screen decor layers.
-            t.apply(true /* sync */);
-        }
-    }
-
-    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
-        proto.write(STARTED, mStarted);
-        proto.write(ANIMATION_RUNNING, mAnimRunning);
-        proto.end(token);
-    }
-
-    boolean hasScreenshot() {
-        return mScreenshotLayer != null;
-    }
-
-    private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
-        if (mScreenshotLayer == null) {
-            return;
-        }
-        matrix.getValues(mTmpFloats);
-        float x = mTmpFloats[Matrix.MTRANS_X];
-        float y = mTmpFloats[Matrix.MTRANS_Y];
-        t.setPosition(mScreenshotLayer, x, y);
-        t.setMatrix(mScreenshotLayer,
-                mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
-                mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
-
-        t.setAlpha(mScreenshotLayer, (float) 1.0);
-        t.show(mScreenshotLayer);
-    }
-
-    public void printTo(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer);
-        pw.print(prefix);
-        pw.print("mEnteringBlackFrame=");
-        pw.println(mEnteringBlackFrame);
-        if (mEnteringBlackFrame != null) {
-            mEnteringBlackFrame.printTo(prefix + "  ", pw);
-        }
-        pw.print(prefix); pw.print("mCurRotation="); pw.print(mCurRotation);
-        pw.print(" mOriginalRotation="); pw.println(mOriginalRotation);
-        pw.print(prefix); pw.print("mOriginalWidth="); pw.print(mOriginalWidth);
-        pw.print(" mOriginalHeight="); pw.println(mOriginalHeight);
-        pw.print(prefix); pw.print("mStarted="); pw.print(mStarted);
-        pw.print(" mAnimRunning="); pw.print(mAnimRunning);
-        pw.print(" mFinishAnimReady="); pw.print(mFinishAnimReady);
-        pw.print(" mFinishAnimStartTime="); pw.println(mFinishAnimStartTime);
-        pw.print(prefix); pw.print("mRotateExitAnimation="); pw.print(mRotateExitAnimation);
-        pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println();
-        pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation);
-        pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println();
-        pw.print(prefix); pw.print("mSnapshotInitialMatrix=");
-        mSnapshotInitialMatrix.dump(pw); pw.println();
-    }
-
-    public void setRotation(SurfaceControl.Transaction t, int rotation) {
-        mCurRotation = rotation;
-
-        // Compute the transformation matrix that must be applied
-        // to the snapshot to make it stay in the same original position
-        // with the current screen rotation.
-        int delta = deltaRotation(rotation, mOriginalRotation);
-        computeRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mSnapshotInitialMatrix);
-        setRotationTransform(t, mSnapshotInitialMatrix);
-    }
-
-    /**
-     * Returns true if animating.
-     */
-    private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration,
-            float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
-        if (mScreenshotLayer == null) {
-            // Can't do animation.
-            return false;
-        }
-        if (mStarted) {
-            return true;
-        }
-
-        mStarted = true;
-
-        // Figure out how the screen has moved from the original rotation.
-        int delta = deltaRotation(mCurRotation, mOriginalRotation);
-
-        final boolean customAnim;
-        if (exitAnim != 0 && enterAnim != 0) {
-            customAnim = true;
-            mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
-            mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
-            mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
-                    R.anim.screen_rotate_alpha);
-        } else {
-            customAnim = false;
-            switch (delta) { /* Counter-Clockwise Rotations */
-                case Surface.ROTATION_0:
-                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_0_exit);
-                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.rotation_animation_enter);
-                    break;
-                case Surface.ROTATION_90:
-                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_plus_90_exit);
-                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_plus_90_enter);
-                    break;
-                case Surface.ROTATION_180:
-                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_180_exit);
-                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_180_enter);
-                    break;
-                case Surface.ROTATION_270:
-                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_minus_90_exit);
-                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_minus_90_enter);
-                    break;
-            }
-        }
-
-        ProtoLog.d(WM_DEBUG_ORIENTATION, "Start rotation animation. customAnim=%s, "
-                        + "mCurRotation=%s, mOriginalRotation=%s",
-                customAnim, Surface.rotationToString(mCurRotation),
-                Surface.rotationToString(mOriginalRotation));
-
-        mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
-        mRotateExitAnimation.restrictDuration(maxAnimationDuration);
-        mRotateExitAnimation.scaleCurrentDuration(animationScale);
-        mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
-        mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
-        mRotateEnterAnimation.scaleCurrentDuration(animationScale);
-
-        mAnimRunning = false;
-        mFinishAnimReady = false;
-        mFinishAnimStartTime = -1;
-
-        if (customAnim) {
-            mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
-            mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
-        }
-
-        if (customAnim && mEnteringBlackFrame == null) {
-            try {
-                Rect outer = new Rect(-finalWidth, -finalHeight,
-                        finalWidth * 2, finalHeight * 2);
-                Rect inner = new Rect(0, 0, finalWidth, finalHeight);
-                mEnteringBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
-                        SCREEN_FREEZE_LAYER_BASE, mDisplayContent, false, mEnterBlackFrameLayer);
-            } catch (OutOfResourcesException e) {
-                Slog.w(TAG, "Unable to allocate black surface", e);
-            }
-        }
-
-        if (customAnim) {
-            mSurfaceRotationAnimationController.startCustomAnimation();
-        } else {
-            mSurfaceRotationAnimationController.startScreenRotationAnimation();
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns true if animating.
-     */
-    public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration,
-            float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
-        if (mScreenshotLayer == null) {
-            // Can't do animation.
-            return false;
-        }
-        if (!mStarted) {
-            mEndLuma = TransitionAnimation.getBorderLuma(mDisplayContent.getWindowingLayer(),
-                    finalWidth, finalHeight);
-            startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight,
-                    exitAnim, enterAnim);
-        }
-        if (!mStarted) {
-            return false;
-        }
-        mFinishAnimReady = true;
-        return true;
-    }
-
-    public void kill() {
-        if (mSurfaceRotationAnimationController != null) {
-            mSurfaceRotationAnimationController.cancel();
-            mSurfaceRotationAnimationController = null;
-        }
-
-        if (mScreenshotLayer != null) {
-            ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "  FREEZE %s: DESTROY", mScreenshotLayer);
-            SurfaceControl.Transaction t = mService.mTransactionFactory.get();
-            if (mScreenshotLayer.isValid()) {
-                t.remove(mScreenshotLayer);
-            }
-            mScreenshotLayer = null;
-
-            if (mEnterBlackFrameLayer != null) {
-                if (mEnterBlackFrameLayer.isValid()) {
-                    t.remove(mEnterBlackFrameLayer);
-                }
-                mEnterBlackFrameLayer = null;
-            }
-            if (mBackColorSurface != null) {
-                if (mBackColorSurface.isValid()) {
-                    t.remove(mBackColorSurface);
-                }
-                mBackColorSurface = null;
-            }
-
-            if (mRoundedCornerOverlay != null) {
-                if (mDisplayContent.getRotationAnimation() == null
-                        || mDisplayContent.getRotationAnimation() == this) {
-                    setSkipScreenshotForRoundedCornerOverlays(true, t);
-                    for (SurfaceControl sc : mRoundedCornerOverlay) {
-                        if (sc.isValid()) {
-                            t.show(sc);
-                        }
-                    }
-                }
-                mRoundedCornerOverlay = null;
-            }
-            t.apply();
-        }
-
-        if (mEnteringBlackFrame != null) {
-            mEnteringBlackFrame.kill();
-            mEnteringBlackFrame = null;
-        }
-        if (mRotateExitAnimation != null) {
-            mRotateExitAnimation.cancel();
-            mRotateExitAnimation = null;
-        }
-        if (mRotateEnterAnimation != null) {
-            mRotateEnterAnimation.cancel();
-            mRotateEnterAnimation = null;
-        }
-        if (mRotateAlphaAnimation != null) {
-            mRotateAlphaAnimation.cancel();
-            mRotateAlphaAnimation = null;
-        }
-    }
-
-    public boolean isAnimating() {
-        return mSurfaceRotationAnimationController != null
-                && mSurfaceRotationAnimationController.isAnimating();
-    }
-
-    public boolean isRotating() {
-        return mCurRotation != mOriginalRotation;
-    }
-
-    /**
-     * Utility class that runs a {@link ScreenRotationAnimation} on the {@link
-     * SurfaceAnimationRunner}.
-     * <p>
-     * The rotation animation supports both screen rotation and custom animations
-     *
-     * For custom animations:
-     * <ul>
-     *   <li>
-     *     The screenshot layer which has an added animation of it's alpha channel
-     *     ("screen_rotate_alpha") and that will be applied along with the custom animation.
-     *   </li>
-     *   <li> A device layer that is animated with the provided custom animation </li>
-     * </ul>
-     *
-     * For screen rotation:
-     * <ul>
-     *   <li> A rotation layer that is both rotated and faded out during a single animation </li>
-     *   <li> A device layer that is both rotated and faded in during a single animation </li>
-     *   <li> A background color layer that transitions colors behind the first two layers </li>
-     * </ul>
-     *
-     * {@link ScreenRotationAnimation#startAnimation(
-     *     SurfaceControl.Transaction, long, float, int, int, int, int)}.
-     * </ul>
-     *
-     * <p>
-     * Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of
-     * this three {@link SurfaceControl}s which then delegates the animation to the
-     * {@link ScreenRotationAnimation}.
-     */
-    class SurfaceRotationAnimationController {
-        private SurfaceAnimator mDisplayAnimator;
-        private SurfaceAnimator mScreenshotRotationAnimator;
-        private SurfaceAnimator mRotateScreenAnimator;
-        private SurfaceAnimator mEnterBlackFrameAnimator;
-
-        void startCustomAnimation() {
-            try {
-                mService.mSurfaceAnimationRunner.deferStartingAnimations();
-                mRotateScreenAnimator = startScreenshotAlphaAnimation();
-                mDisplayAnimator = startDisplayRotation();
-                if (mEnteringBlackFrame != null) {
-                    mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
-                }
-            } finally {
-                mService.mSurfaceAnimationRunner.continueStartingAnimations();
-            }
-        }
-
-        /**
-         * Start the rotation animation of the display and the screenshot on the
-         * {@link SurfaceAnimationRunner}.
-         */
-        void startScreenRotationAnimation() {
-            try {
-                mService.mSurfaceAnimationRunner.deferStartingAnimations();
-                mDisplayAnimator = startDisplayRotation();
-                mScreenshotRotationAnimator = startScreenshotRotationAnimation();
-                startColorAnimation();
-            } finally {
-                mService.mSurfaceAnimationRunner.continueStartingAnimations();
-            }
-        }
-
-        private SimpleSurfaceAnimatable.Builder initializeBuilder() {
-            return new SimpleSurfaceAnimatable.Builder()
-                    .setSyncTransactionSupplier(mDisplayContent::getSyncTransaction)
-                    .setPendingTransactionSupplier(mDisplayContent::getPendingTransaction)
-                    .setCommitTransactionRunnable(mDisplayContent::commitPendingTransaction)
-                    .setAnimationLeashSupplier(mDisplayContent::makeOverlay);
-        }
-
-        private SurfaceAnimator startDisplayRotation() {
-            SurfaceAnimator animator = startAnimation(initializeBuilder()
-                            .setAnimationLeashParent(mDisplayContent.getSurfaceControl())
-                            .setSurfaceControl(mDisplayContent.getWindowingLayer())
-                            .setParentSurfaceControl(mDisplayContent.getSurfaceControl())
-                            .setWidth(mDisplayContent.getSurfaceWidth())
-                            .setHeight(mDisplayContent.getSurfaceHeight())
-                            .build(),
-                    createWindowAnimationSpec(mRotateEnterAnimation),
-                    this::onAnimationEnd);
-
-            // Crop the animation leash to avoid extended wallpaper from showing over
-            // mBackColorSurface
-            Rect displayBounds = mDisplayContent.getBounds();
-            mDisplayContent.getPendingTransaction()
-                    .setWindowCrop(animator.mLeash, displayBounds.width(), displayBounds.height());
-            return animator;
-        }
-
-        private SurfaceAnimator startScreenshotAlphaAnimation() {
-            return startAnimation(initializeBuilder()
-                            .setSurfaceControl(mScreenshotLayer)
-                            .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
-                            .setWidth(mDisplayContent.getSurfaceWidth())
-                            .setHeight(mDisplayContent.getSurfaceHeight())
-                            .build(),
-                    createWindowAnimationSpec(mRotateAlphaAnimation),
-                    this::onAnimationEnd);
-        }
-
-        private SurfaceAnimator startEnterBlackFrameAnimation() {
-            return startAnimation(initializeBuilder()
-                            .setSurfaceControl(mEnterBlackFrameLayer)
-                            .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
-                            .build(),
-                    createWindowAnimationSpec(mRotateEnterAnimation),
-                    this::onAnimationEnd);
-        }
-
-        private SurfaceAnimator startScreenshotRotationAnimation() {
-            return startAnimation(initializeBuilder()
-                            .setSurfaceControl(mScreenshotLayer)
-                            .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
-                            .build(),
-                    createWindowAnimationSpec(mRotateExitAnimation),
-                    this::onAnimationEnd);
-        }
-
-
-        /**
-         * Applies the color change from {@link #mStartLuma} to {@link #mEndLuma} as a
-         * grayscale color
-         */
-        private void startColorAnimation() {
-            int colorTransitionMs = mContext.getResources().getInteger(
-                    R.integer.config_screen_rotation_color_transition);
-            final SurfaceAnimationRunner runner = mService.mSurfaceAnimationRunner;
-            final float[] rgbTmpFloat = new float[3];
-            final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
-            final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
-            final long duration = colorTransitionMs * (long) mService.getCurrentAnimatorScale();
-            final ArgbEvaluator va = ArgbEvaluator.getInstance();
-            runner.startAnimation(
-                new LocalAnimationAdapter.AnimationSpec() {
-                    @Override
-                    public long getDuration() {
-                        return duration;
-                    }
-
-                    @Override
-                    public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
-                        long currentPlayTime) {
-                        final float fraction = getFraction(currentPlayTime);
-                        final int color = (Integer) va.evaluate(fraction, startColor, endColor);
-                        Color middleColor = Color.valueOf(color);
-                        rgbTmpFloat[0] = middleColor.red();
-                        rgbTmpFloat[1] = middleColor.green();
-                        rgbTmpFloat[2] = middleColor.blue();
-                        if (leash.isValid()) {
-                            t.setColor(leash, rgbTmpFloat);
-                        }
-                    }
-
-                    @Override
-                    public void dump(PrintWriter pw, String prefix) {
-                        pw.println(prefix + "startLuma=" + mStartLuma
-                                + " endLuma=" + mEndLuma
-                                + " durationMs=" + colorTransitionMs);
-                    }
-
-                    @Override
-                    public void dumpDebugInner(ProtoOutputStream proto) {
-                        final long token = proto.start(ROTATE);
-                        proto.write(START_LUMA, mStartLuma);
-                        proto.write(END_LUMA, mEndLuma);
-                        proto.write(DURATION_MS, colorTransitionMs);
-                        proto.end(token);
-                    }
-                },
-                mBackColorSurface, mDisplayContent.getPendingTransaction(), null);
-        }
-
-        private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) {
-            return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */,
-                    false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */);
-        }
-
-        /**
-         * Start an animation defined by animationSpec on a new {@link SurfaceAnimator}.
-         *
-         * @param animatable The animatable used for the animation.
-         * @param animationSpec The spec of the animation.
-         * @param animationFinishedCallback Callback passed to the {@link SurfaceAnimator}
-         *                                    and called when the animation finishes.
-         * @return The newly created {@link SurfaceAnimator} that as been started.
-         */
-        private SurfaceAnimator startAnimation(
-                SurfaceAnimator.Animatable animatable,
-                LocalAnimationAdapter.AnimationSpec animationSpec,
-                OnAnimationFinishedCallback animationFinishedCallback) {
-            SurfaceAnimator animator = new SurfaceAnimator(
-                    animatable, animationFinishedCallback, mService);
-
-            LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter(
-                    animationSpec, mService.mSurfaceAnimationRunner);
-            animator.startAnimation(mDisplayContent.getPendingTransaction(),
-                    localAnimationAdapter, false, ANIMATION_TYPE_SCREEN_ROTATION);
-            return animator;
-        }
-
-        private void onAnimationEnd(@AnimationType int type, AnimationAdapter anim) {
-            synchronized (mService.mGlobalLock) {
-                if (isAnimating()) {
-                    ProtoLog.v(WM_DEBUG_ORIENTATION,
-                            "ScreenRotation still animating: type: %d\n"
-                                    + "mDisplayAnimator: %s\n"
-                                    + "mEnterBlackFrameAnimator: %s\n"
-                                    + "mRotateScreenAnimator: %s\n"
-                                    + "mScreenshotRotationAnimator: %s",
-                            type,
-                            mDisplayAnimator != null
-                                    ? mDisplayAnimator.isAnimating() : null,
-                            mEnterBlackFrameAnimator != null
-                                    ? mEnterBlackFrameAnimator.isAnimating() : null,
-                            mRotateScreenAnimator != null
-                                    ? mRotateScreenAnimator.isAnimating() : null,
-                            mScreenshotRotationAnimator != null
-                                    ? mScreenshotRotationAnimator.isAnimating() : null
-                    );
-                    return;
-                }
-                ProtoLog.d(WM_DEBUG_ORIENTATION, "ScreenRotationAnimation onAnimationEnd");
-                mEnterBlackFrameAnimator = null;
-                mScreenshotRotationAnimator = null;
-                mRotateScreenAnimator = null;
-                mService.mAnimator.mBulkUpdateParams |= WindowSurfacePlacer.SET_UPDATE_ROTATION;
-                if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
-                    // It also invokes kill().
-                    mDisplayContent.setRotationAnimation(null);
-                    mDisplayContent.mAppCompatCameraPolicy.onScreenRotationAnimationFinished();
-                } else {
-                    kill();
-                }
-                mService.updateRotation(false, false);
-            }
-        }
-
-        public void cancel() {
-            if (mEnterBlackFrameAnimator != null) {
-                mEnterBlackFrameAnimator.cancelAnimation();
-            }
-            if (mScreenshotRotationAnimator != null) {
-                mScreenshotRotationAnimator.cancelAnimation();
-            }
-
-            if (mRotateScreenAnimator != null) {
-                mRotateScreenAnimator.cancelAnimation();
-            }
-
-            if (mDisplayAnimator != null) {
-                mDisplayAnimator.cancelAnimation();
-            }
-
-            if (mBackColorSurface != null) {
-                mService.mSurfaceAnimationRunner.onAnimationCancelled(mBackColorSurface);
-            }
-        }
-
-        public boolean isAnimating() {
-            return mDisplayAnimator != null && mDisplayAnimator.isAnimating()
-                    || mEnterBlackFrameAnimator != null && mEnterBlackFrameAnimator.isAnimating()
-                    || mRotateScreenAnimator != null && mRotateScreenAnimator.isAnimating()
-                    || mScreenshotRotationAnimator != null
-                    && mScreenshotRotationAnimator.isAnimating();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java b/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java
deleted file mode 100644
index 3b3db89..0000000
--- a/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * Copyright (C) 2019 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.wm;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.view.SurfaceControl;
-
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/**
- * An implementation of {@link SurfaceAnimator.Animatable} that is instantiated
- * using a builder pattern for more convenience over reimplementing the whole interface.
- * <p>
- * Use {@link SimpleSurfaceAnimatable.Builder} to create a new instance of this class.
- *
- * @see com.android.server.wm.SurfaceAnimator.Animatable
- */
-public class SimpleSurfaceAnimatable implements SurfaceAnimator.Animatable {
-    private final int mWidth;
-    private final int mHeight;
-    private final boolean mShouldDeferAnimationFinish;
-    private final SurfaceControl mAnimationLeashParent;
-    private final SurfaceControl mSurfaceControl;
-    private final SurfaceControl mParentSurfaceControl;
-    private final Runnable mCommitTransactionRunnable;
-    private final Supplier<SurfaceControl.Builder> mAnimationLeashFactory;
-    private final Supplier<SurfaceControl.Transaction> mSyncTransaction;
-    private final Supplier<SurfaceControl.Transaction> mPendingTransaction;
-    private final BiConsumer<SurfaceControl.Transaction, SurfaceControl> mOnAnimationLeashCreated;
-    private final Consumer<SurfaceControl.Transaction> mOnAnimationLeashLost;
-    private final Consumer<Runnable> mOnAnimationFinished;
-
-    /**
-     * Use {@link SimpleSurfaceAnimatable.Builder} to create a new instance.
-     */
-    private SimpleSurfaceAnimatable(Builder builder) {
-        mWidth = builder.mWidth;
-        mHeight = builder.mHeight;
-        mShouldDeferAnimationFinish = builder.mShouldDeferAnimationFinish;
-        mAnimationLeashParent = builder.mAnimationLeashParent;
-        mSurfaceControl = builder.mSurfaceControl;
-        mParentSurfaceControl = builder.mParentSurfaceControl;
-        mCommitTransactionRunnable = builder.mCommitTransactionRunnable;
-        mAnimationLeashFactory = builder.mAnimationLeashFactory;
-        mOnAnimationLeashCreated = builder.mOnAnimationLeashCreated;
-        mOnAnimationLeashLost = builder.mOnAnimationLeashLost;
-        mSyncTransaction = builder.mSyncTransactionSupplier;
-        mPendingTransaction = builder.mPendingTransactionSupplier;
-        mOnAnimationFinished = builder.mOnAnimationFinished;
-    }
-
-    @Override
-    public SurfaceControl.Transaction getSyncTransaction() {
-        return mSyncTransaction.get();
-    }
-
-    @NonNull
-    @Override
-    public SurfaceControl.Transaction getPendingTransaction() {
-        return mPendingTransaction.get();
-    }
-
-    @Override
-    public void commitPendingTransaction() {
-        mCommitTransactionRunnable.run();
-    }
-
-    @Override
-    public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
-        if (mOnAnimationLeashCreated != null) {
-            mOnAnimationLeashCreated.accept(t, leash);
-        }
-
-    }
-
-    @Override
-    public void onAnimationLeashLost(SurfaceControl.Transaction t) {
-        if (mOnAnimationLeashLost != null) {
-            mOnAnimationLeashLost.accept(t);
-        }
-    }
-
-    @Override
-    @NonNull
-    public SurfaceControl.Builder makeAnimationLeash() {
-        return mAnimationLeashFactory.get();
-    }
-
-    @Override
-    public SurfaceControl getAnimationLeashParent() {
-        return mAnimationLeashParent;
-    }
-
-    @Override
-    @Nullable
-    public SurfaceControl getSurfaceControl() {
-        return mSurfaceControl;
-    }
-
-    @Override
-    public SurfaceControl getParentSurfaceControl() {
-        return mParentSurfaceControl;
-    }
-
-    @Override
-    public int getSurfaceWidth() {
-        return mWidth;
-    }
-
-    @Override
-    public int getSurfaceHeight() {
-        return mHeight;
-    }
-
-    @Override
-    public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
-        if (mOnAnimationFinished != null) {
-            mOnAnimationFinished.accept(endDeferFinishCallback);
-        }
-        return mShouldDeferAnimationFinish;
-    }
-
-    /**
-     * Builder class to create a {@link SurfaceAnimator.Animatable} without having to
-     * create a new class that implements the interface.
-     */
-    static class Builder {
-        private int mWidth = -1;
-        private int mHeight = -1;
-        private boolean mShouldDeferAnimationFinish = false;
-
-        @Nullable
-        private SurfaceControl mAnimationLeashParent = null;
-
-        @Nullable
-        private SurfaceControl mSurfaceControl = null;
-
-        @Nullable
-        private SurfaceControl mParentSurfaceControl = null;
-        private Runnable mCommitTransactionRunnable;
-
-        @Nullable
-        private BiConsumer<SurfaceControl.Transaction, SurfaceControl> mOnAnimationLeashCreated =
-                null;
-
-        @Nullable
-        private Consumer<SurfaceControl.Transaction> mOnAnimationLeashLost = null;
-
-        @Nullable
-        private Consumer<Runnable> mOnAnimationFinished = null;
-
-        @NonNull
-        private Supplier<SurfaceControl.Transaction> mSyncTransactionSupplier;
-
-        @NonNull
-        private Supplier<SurfaceControl.Transaction> mPendingTransactionSupplier;
-
-        @NonNull
-        private Supplier<SurfaceControl.Builder> mAnimationLeashFactory;
-
-        /**
-         * Set the runnable to be called when
-         * {@link SurfaceAnimator.Animatable#commitPendingTransaction()}
-         * is called.
-         *
-         * @see SurfaceAnimator.Animatable#commitPendingTransaction()
-         */
-        public SimpleSurfaceAnimatable.Builder setCommitTransactionRunnable(
-                @NonNull Runnable commitTransactionRunnable) {
-            mCommitTransactionRunnable = commitTransactionRunnable;
-            return this;
-        }
-
-        /**
-         * Set the callback called when
-         * {@link SurfaceAnimator.Animatable#onAnimationLeashCreated(SurfaceControl.Transaction,
-         * SurfaceControl)} is called
-         *
-         * @see SurfaceAnimator.Animatable#onAnimationLeashCreated(SurfaceControl.Transaction,
-         * SurfaceControl)
-         */
-        public SimpleSurfaceAnimatable.Builder setOnAnimationLeashCreated(
-                @Nullable BiConsumer<SurfaceControl.Transaction, SurfaceControl>
-                        onAnimationLeashCreated) {
-            mOnAnimationLeashCreated = onAnimationLeashCreated;
-            return this;
-        }
-
-        /**
-         * Set the callback called when
-         * {@link SurfaceAnimator.Animatable#onAnimationLeashLost(SurfaceControl.Transaction)}
-         * (SurfaceControl.Transaction, SurfaceControl)} is called
-         *
-         * @see SurfaceAnimator.Animatable#onAnimationLeashLost(SurfaceControl.Transaction)
-         */
-        public SimpleSurfaceAnimatable.Builder setOnAnimationLeashLost(
-                @Nullable Consumer<SurfaceControl.Transaction> onAnimationLeashLost) {
-            mOnAnimationLeashLost = onAnimationLeashLost;
-            return this;
-        }
-
-        /**
-         * @see SurfaceAnimator.Animatable#getSyncTransaction()
-         */
-        public Builder setSyncTransactionSupplier(
-                @NonNull Supplier<SurfaceControl.Transaction> syncTransactionSupplier) {
-            mSyncTransactionSupplier = syncTransactionSupplier;
-            return this;
-        }
-
-        /**
-         * @see SurfaceAnimator.Animatable#getPendingTransaction()
-         */
-        public Builder setPendingTransactionSupplier(
-                @NonNull Supplier<SurfaceControl.Transaction> pendingTransactionSupplier) {
-            mPendingTransactionSupplier = pendingTransactionSupplier;
-            return this;
-        }
-
-        /**
-         * Set the {@link Supplier} responsible for creating a new animation leash.
-         *
-         * @see SurfaceAnimator.Animatable#makeAnimationLeash()
-         */
-        public SimpleSurfaceAnimatable.Builder setAnimationLeashSupplier(
-                @NonNull Supplier<SurfaceControl.Builder> animationLeashFactory) {
-            mAnimationLeashFactory = animationLeashFactory;
-            return this;
-        }
-
-        /**
-         * @see SurfaceAnimator.Animatable#getAnimationLeashParent()
-         */
-        public SimpleSurfaceAnimatable.Builder setAnimationLeashParent(
-                SurfaceControl animationLeashParent) {
-            mAnimationLeashParent = animationLeashParent;
-            return this;
-        }
-
-        /**
-         * @see SurfaceAnimator.Animatable#getSurfaceControl()
-         */
-        public SimpleSurfaceAnimatable.Builder setSurfaceControl(
-                @NonNull SurfaceControl surfaceControl) {
-            mSurfaceControl = surfaceControl;
-            return this;
-        }
-
-        /**
-         * @see SurfaceAnimator.Animatable#getParentSurfaceControl()
-         */
-        public SimpleSurfaceAnimatable.Builder setParentSurfaceControl(
-                SurfaceControl parentSurfaceControl) {
-            mParentSurfaceControl = parentSurfaceControl;
-            return this;
-        }
-
-        /**
-         * Default to -1.
-         *
-         * @see SurfaceAnimator.Animatable#getSurfaceWidth()
-         */
-        public SimpleSurfaceAnimatable.Builder setWidth(int width) {
-            mWidth = width;
-            return this;
-        }
-
-        /**
-         * Default to -1.
-         *
-         * @see SurfaceAnimator.Animatable#getSurfaceHeight()
-         */
-        public SimpleSurfaceAnimatable.Builder setHeight(int height) {
-            mHeight = height;
-            return this;
-        }
-
-        /**
-         * Set the value returned by
-         * {@link SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)}.
-         *
-         * @param onAnimationFinish will be called with the runnable to execute when the animation
-         *                          needs to be finished.
-         * @see SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)
-         */
-        public SimpleSurfaceAnimatable.Builder setShouldDeferAnimationFinish(
-                boolean shouldDeferAnimationFinish,
-                @Nullable Consumer<Runnable> onAnimationFinish) {
-            mShouldDeferAnimationFinish = shouldDeferAnimationFinish;
-            mOnAnimationFinished = onAnimationFinish;
-            return this;
-        }
-
-        public SurfaceAnimator.Animatable build() {
-            if (mSyncTransactionSupplier == null) {
-                throw new IllegalArgumentException("mSyncTransactionSupplier cannot be null");
-            }
-            if (mPendingTransactionSupplier == null) {
-                throw new IllegalArgumentException("mPendingTransactionSupplier cannot be null");
-            }
-            if (mAnimationLeashFactory == null) {
-                throw new IllegalArgumentException("mAnimationLeashFactory cannot be null");
-            }
-            if (mCommitTransactionRunnable == null) {
-                throw new IllegalArgumentException("mCommitTransactionRunnable cannot be null");
-            }
-            if (mSurfaceControl == null) {
-                throw new IllegalArgumentException("mSurfaceControl cannot be null");
-            }
-            return new SimpleSurfaceAnimatable(this);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7a88338..c6136f3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5101,6 +5101,7 @@
         mTranslucentActivityWaiting = r;
         mPendingConvertFromTranslucentActivity = r;
         mUndrawnActivitiesBelowTopTranslucent.clear();
+        updateTaskDescription();
         mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT);
     }
 
@@ -5110,6 +5111,7 @@
                     + " but is " + r);
         }
         mPendingConvertFromTranslucentActivity = null;
+        updateTaskDescription();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 1993053..fc7437b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2092,12 +2092,6 @@
                 ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev);
                 prev = null;
             }
-            // It is possible the activity was freezing the screen before it was paused.
-            // In that case go ahead and remove the freeze this activity has on the screen
-            // since it is no longer visible.
-            if (prev != null) {
-                prev.stopFreezingScreen(true /* unfreezeNow */, true /* force */);
-            }
         }
 
         if (resumeNext) {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index c6a1679..aaae160 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -842,7 +842,6 @@
             return;
         }
         validateAndGetState(organizer);
-        Slog.w(TAG, "onTaskFragmentError ", exception);
         addPendingEvent(new PendingTaskFragmentEvent.Builder(
                 PendingTaskFragmentEvent.EVENT_ERROR, organizer)
                 .setErrorCallbackToken(errorCallbackToken)
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 75cefdf..37cc0d2 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1626,6 +1626,11 @@
         for (int i = 0; i < mConfigAtEndActivities.size(); ++i) {
             final ActivityRecord target = mConfigAtEndActivities.get(i);
             final SurfaceControl targetLeash = target.getSurfaceControl();
+            if (targetLeash == null) {
+                // activity may have been removed. In this case, no need to sync, just update state.
+                target.resumeConfigurationDispatch();
+                continue;
+            }
             if (target.getSyncGroup() == null || target.getSyncGroup().isIgnoring(target)) {
                 if (syncId < 0) {
                     final BLASTSyncEngine.SyncGroup sg = mSyncEngine.prepareSyncSet(
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 790ae1e..80137a2 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -146,7 +146,6 @@
         boolean rootAnimating = false;
         mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
         mBulkUpdateParams = 0;
-        root.mOrientationChangeComplete = true;
         if (DEBUG_WINDOW_TRACE) {
             Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
         }
@@ -203,8 +202,7 @@
         }
 
         final boolean hasPendingLayoutChanges = root.hasPendingLayoutChanges(this);
-        final boolean doRequest = (mBulkUpdateParams != 0 || root.mOrientationChangeComplete)
-                && root.copyAnimToLayoutParams();
+        final boolean doRequest = mBulkUpdateParams != 0 && root.copyAnimToLayoutParams();
         if (hasPendingLayoutChanges || doRequest) {
             mService.mWindowPlacerLocked.requestTraversal();
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1754d73..0e9b423 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -31,7 +31,6 @@
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
-import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
@@ -116,13 +115,11 @@
 import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
 import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_APPLIED;
-import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.LockGuard.installLock;
 import static com.android.server.policy.PhoneWindowManager.TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerService.DEMOTE_TOP_REASON_EXPANDED_NOTIFICATION_SHADE;
-import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
@@ -151,7 +148,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListener;
 import static com.android.server.wm.WindowManagerServiceDumpProto.BACK_NAVIGATION;
-import static com.android.server.wm.WindowManagerServiceDumpProto.DISPLAY_FROZEN;
 import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP;
 import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_DISPLAY_ID;
 import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_WINDOW;
@@ -251,7 +247,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
-import android.util.TimeUtils;
 import android.util.TypedValue;
 import android.util.proto.ProtoOutputStream;
 import android.view.Choreographer;
@@ -403,8 +398,6 @@
 
     static final int LAYOUT_REPEAT_THRESHOLD = 4;
 
-    static final boolean PROFILE_ORIENTATION = false;
-
     /** The maximum length we will accept for a loaded animation duration:
      * this is 10 seconds.
      */
@@ -742,19 +735,8 @@
     @Nullable
     private Runnable mPointerDownOutsideFocusRunnable;
 
-    boolean mDisplayFrozen = false;
-    long mDisplayFreezeTime = 0;
-    int mLastDisplayFreezeDuration = 0;
-    Object mLastFinishedFreezeSource = null;
     boolean mSwitchingUser = false;
 
-    final static int WINDOWS_FREEZING_SCREENS_NONE = 0;
-    final static int WINDOWS_FREEZING_SCREENS_ACTIVE = 1;
-    final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2;
-    int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
-
-    int mAppsFreezingScreen = 0;
-
     @VisibleForTesting
     boolean mPerDisplayFocusEnabled;
 
@@ -764,11 +746,6 @@
     // Number of windows whose insets state have been changed.
     int mWindowsInsetsChanged = 0;
 
-    // This is held as long as we have the screen frozen, to give us time to
-    // perform a rotation animation when turning off shows the lock screen which
-    // changes the orientation.
-    private final PowerManager.WakeLock mScreenFrozenLock;
-
     final TaskSnapshotController mTaskSnapshotController;
     final SnapshotController mSnapshotController;
 
@@ -1053,12 +1030,6 @@
 
     final DragDropController mDragDropController;
 
-    /** For frozen screen animations. */
-    private int mExitAnimId, mEnterAnimId;
-
-    /** The display that the rotation animation is applying to. */
-    private int mFrozenDisplayId = INVALID_DISPLAY;
-
     /** Skip repeated ActivityRecords initialization. Note that AppWindowsToken's version of this
      * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
     int mTransactionSequence;
@@ -1161,12 +1132,6 @@
         }
     };
 
-    final ArrayList<AppFreezeListener> mAppFreezeListeners = new ArrayList<>();
-
-    interface AppFreezeListener {
-        void onAppFreezeTimeout();
-    }
-
     private final ScreenRecordingCallbackController mScreenRecordingCallbackController;
 
     private volatile boolean mDisableSecureWindows = false;
@@ -1347,9 +1312,6 @@
             mAnimationsDisabled = mPowerManagerInternal
                     .getLowPowerState(ServiceType.ANIMATION).batterySaverEnabled;
         }
-        mScreenFrozenLock = mPowerManager.newWakeLock(
-                PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
-        mScreenFrozenLock.setReferenceCounted(false);
 
         mRotationWatcherController = new RotationWatcherController(this);
         mDisplayNotificationController = new DisplayWindowListenerController(this);
@@ -5601,11 +5563,8 @@
     // -------------------------------------------------------------
 
     final class H extends android.os.Handler {
-        public static final int WINDOW_FREEZE_TIMEOUT = 11;
-
         public static final int PERSIST_ANIMATION_SCALE = 14;
         public static final int ENABLE_SCREEN = 16;
-        public static final int APP_FREEZE_TIMEOUT = 17;
         public static final int REPORT_WINDOWS_CHANGE = 19;
 
         public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
@@ -5645,14 +5604,6 @@
                 Slog.v(TAG_WM, "handleMessage: entry what=" + msg.what);
             }
             switch (msg.what) {
-                case WINDOW_FREEZE_TIMEOUT: {
-                    final DisplayContent displayContent = (DisplayContent) msg.obj;
-                    synchronized (mGlobalLock) {
-                        displayContent.onWindowFreezeTimeout();
-                    }
-                    break;
-                }
-
                 case PERSIST_ANIMATION_SCALE: {
                     Settings.Global.putFloat(mContext.getContentResolver(),
                             Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
@@ -5691,17 +5642,6 @@
                     break;
                 }
 
-                case APP_FREEZE_TIMEOUT: {
-                    synchronized (mGlobalLock) {
-                        ProtoLog.w(WM_ERROR, "App freeze timeout expired.");
-                        mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
-                        for (int i = mAppFreezeListeners.size() - 1; i >= 0; --i) {
-                            mAppFreezeListeners.get(i).onAppFreezeTimeout();
-                        }
-                    }
-                    break;
-                }
-
                 case REPORT_WINDOWS_CHANGE: {
                     if (mWindowsChanged) {
                         synchronized (mGlobalLock) {
@@ -6310,22 +6250,6 @@
         return win;
     }
 
-    void makeWindowFreezingScreenIfNeededLocked(WindowState w) {
-        // If the screen is currently frozen, then keep it frozen until this window draws at its
-        // new orientation.
-        if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId()
-                && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
-            ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w);
-            if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
-                mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
-                // XXX should probably keep timeout from
-                // when we first froze the display.
-                mH.sendNewMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, w.getDisplayContent(),
-                        WINDOW_FREEZE_TIMEOUT_DURATION);
-            }
-        }
-    }
-
     void checkDrawnWindowsLocked() {
         if (mWaitingForDrawnCallbacks.isEmpty()) {
             return;
@@ -6394,188 +6318,6 @@
         return changed;
     }
 
-    void startFreezingDisplay(int exitAnim, int enterAnim) {
-        startFreezingDisplay(exitAnim, enterAnim, getDefaultDisplayContentLocked());
-    }
-
-    void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent) {
-        startFreezingDisplay(exitAnim, enterAnim, displayContent,
-                ROTATION_UNDEFINED /* overrideOriginalRotation */);
-    }
-
-    void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
-            int overrideOriginalRotation) {
-        if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) {
-            return;
-        }
-
-        if (!displayContent.isReady() || !displayContent.getDisplayPolicy().isScreenOnFully()
-                || displayContent.getDisplayInfo().state == Display.STATE_OFF
-                || !displayContent.okToAnimate()) {
-            // No need to freeze the screen before the display is ready,  if the screen is off,
-            // or we can't currently animate.
-            return;
-        }
-
-        displayContent.requestDisplayUpdate(() -> {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStartFreezingDisplay");
-            doStartFreezingDisplay(exitAnim, enterAnim, displayContent, overrideOriginalRotation);
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        });
-    }
-
-    private void doStartFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
-            int overrideOriginalRotation) {
-        ProtoLog.d(WM_DEBUG_ORIENTATION,
-                            "startFreezingDisplayLocked: exitAnim=%d enterAnim=%d called by %s",
-                            exitAnim, enterAnim, Debug.getCallers(8));
-        mScreenFrozenLock.acquire();
-        // Apply launch power mode to reduce screen frozen time because orientation change may
-        // relaunch activity and redraw windows. This may also help speed up user switching.
-        mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
-
-        mDisplayFrozen = true;
-        mDisplayFreezeTime = SystemClock.elapsedRealtime();
-        mLastFinishedFreezeSource = null;
-
-        // {@link mDisplayFrozen} prevents us from freezing on multiple displays at the same time.
-        // As a result, we only track the display that has initially froze the screen.
-        mFrozenDisplayId = displayContent.getDisplayId();
-
-        mInputManagerCallback.freezeInputDispatchingLw();
-
-        if (displayContent.mAppTransition.isTransitionSet()) {
-            displayContent.mAppTransition.freeze();
-        }
-
-        if (PROFILE_ORIENTATION) {
-            File file = new File("/data/system/frozen");
-            Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
-        }
-
-        mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
-        mExitAnimId = exitAnim;
-        mEnterAnimId = enterAnim;
-
-        final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED
-                ? overrideOriginalRotation
-                : displayContent.getDisplayInfo().rotation;
-        displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent,
-                originalRotation));
-    }
-
-    void stopFreezingDisplayLocked() {
-        if (!mDisplayFrozen) {
-            return;
-        }
-
-        final DisplayContent displayContent = mRoot.getDisplayContent(mFrozenDisplayId);
-        final int numOpeningApps;
-        final boolean waitingForConfig;
-        final boolean waitingForRemoteDisplayChange;
-        if (displayContent != null) {
-            numOpeningApps = displayContent.mOpeningApps.size();
-            waitingForConfig = displayContent.mWaitingForConfig;
-            waitingForRemoteDisplayChange = displayContent.mRemoteDisplayChangeController
-                    .isWaitingForRemoteDisplayChange();
-        } else {
-            waitingForConfig = waitingForRemoteDisplayChange = false;
-            numOpeningApps = 0;
-        }
-        if (waitingForConfig || waitingForRemoteDisplayChange || mAppsFreezingScreen > 0
-                || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
-                || numOpeningApps > 0) {
-            ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning "
-                    + "waitingForConfig=%b, waitingForRemoteDisplayChange=%b, "
-                    + "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
-                    + "mOpeningApps.size()=%d",
-                    waitingForConfig, waitingForRemoteDisplayChange,
-                    mAppsFreezingScreen, mWindowsFreezingScreen,
-                    numOpeningApps);
-            return;
-        }
-
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStopFreezingDisplayLocked-"
-                + mLastFinishedFreezeSource);
-        doStopFreezingDisplayLocked(displayContent);
-        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-    }
-
-    private void doStopFreezingDisplayLocked(DisplayContent displayContent) {
-        ProtoLog.d(WM_DEBUG_ORIENTATION,
-                    "stopFreezingDisplayLocked: Unfreezing now");
-
-        // We must make a local copy of the displayId as it can be potentially overwritten later on
-        // in this method. For example, {@link startFreezingDisplayLocked} may be called as a result
-        // of update rotation, but we reference the frozen display after that call in this method.
-        mFrozenDisplayId = INVALID_DISPLAY;
-        mDisplayFrozen = false;
-        mInputManagerCallback.thawInputDispatchingLw();
-        mLastDisplayFreezeDuration = (int)(SystemClock.elapsedRealtime() - mDisplayFreezeTime);
-        StringBuilder sb = new StringBuilder(128);
-        sb.append("Screen frozen for ");
-        TimeUtils.formatDuration(mLastDisplayFreezeDuration, sb);
-        if (mLastFinishedFreezeSource != null) {
-            sb.append(" due to ");
-            sb.append(mLastFinishedFreezeSource);
-        }
-        ProtoLog.i(WM_ERROR, "%s", sb.toString());
-        mH.removeMessages(H.APP_FREEZE_TIMEOUT);
-        if (PROFILE_ORIENTATION) {
-            Debug.stopMethodTracing();
-        }
-
-        boolean updateRotation = false;
-
-        ScreenRotationAnimation screenRotationAnimation = displayContent == null ? null
-                : displayContent.getRotationAnimation();
-        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
-            ProtoLog.i(WM_DEBUG_ORIENTATION, "**** Dismissing screen rotation animation");
-            DisplayInfo displayInfo = displayContent.getDisplayInfo();
-            // Get rotation animation again, with new top window
-            if (!displayContent.getDisplayRotation().validateRotationAnimation(
-                    mExitAnimId, mEnterAnimId, false /* forceDefault */)) {
-                mExitAnimId = mEnterAnimId = 0;
-            }
-            if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
-                    getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
-                        displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
-                mTransaction.apply();
-            } else {
-                screenRotationAnimation.kill();
-                displayContent.setRotationAnimation(null);
-                updateRotation = true;
-            }
-        } else {
-            if (screenRotationAnimation != null) {
-                screenRotationAnimation.kill();
-                displayContent.setRotationAnimation(null);
-            }
-            updateRotation = true;
-        }
-
-        boolean configChanged;
-
-        // While the display is frozen we don't re-compute the orientation
-        // to avoid inconsistent states.  However, something interesting
-        // could have actually changed during that time so re-evaluate it
-        // now to catch that.
-        configChanged = displayContent != null && displayContent.updateOrientation();
-
-        mScreenFrozenLock.release();
-
-        if (updateRotation && displayContent != null) {
-            ProtoLog.d(WM_DEBUG_ORIENTATION, "Performing post-rotate rotation");
-            configChanged |= displayContent.updateRotationUnchecked();
-        }
-
-        if (configChanged) {
-            displayContent.sendNewConfiguration();
-        }
-        mAtmService.endPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
-        mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN);
-    }
-
     static int getPropertyInt(String[] tokens, int index, int defUnits, int defDps,
             DisplayMetrics dm) {
         if (index < tokens.length) {
@@ -6876,7 +6618,6 @@
             if (imeWindow != null) {
                 imeWindow.writeIdentifierToProto(proto, INPUT_METHOD_WINDOW);
             }
-            proto.write(DISPLAY_FROZEN, mDisplayFrozen);
             proto.write(FOCUSED_DISPLAY_ID, topFocusedDisplayContent.getDisplayId());
             proto.write(HARD_KEYBOARD_AVAILABLE, mHardKeyboardAvailable);
 
@@ -6999,13 +6740,6 @@
             pw.print(' '); pw.print(dc.mMinSizeOfResizeableTaskDp);
         });
         pw.print("  mBlurEnabled="); pw.println(mBlurController.getBlurEnabled());
-        pw.print("  mLastDisplayFreezeDuration=");
-                TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
-                if ( mLastFinishedFreezeSource != null) {
-                    pw.print(" due to ");
-                    pw.print(mLastFinishedFreezeSource);
-                }
-                pw.println();
         pw.print("  mDisableSecureWindows="); pw.println(mDisableSecureWindows);
 
         mInputManagerCallback.dump(pw, "  ");
@@ -7025,9 +6759,6 @@
             mRoot.dumpLayoutNeededDisplayIds(pw);
 
             pw.print("  mTransactionSequence="); pw.println(mTransactionSequence);
-            pw.print("  mDisplayFrozen="); pw.print(mDisplayFrozen);
-                    pw.print(" windows="); pw.print(mWindowsFreezingScreen);
-                    pw.print(" apps="); pw.println(mAppsFreezingScreen);
             final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
             pw.print("  mRotation="); pw.println(defaultDisplayContent.getRotation());
             pw.print("  mLastOrientation=");
@@ -8905,16 +8636,6 @@
         }
     }
 
-    void registerAppFreezeListener(AppFreezeListener listener) {
-        if (!mAppFreezeListeners.contains(listener)) {
-            mAppFreezeListeners.add(listener);
-        }
-    }
-
-    void unregisterAppFreezeListener(AppFreezeListener listener) {
-        mAppFreezeListeners.remove(listener);
-    }
-
     /** Called to inform window manager if non-Vr UI shoul be disabled or not. */
     public void disableNonVrUi(boolean disable) {
         synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3c3a180..a1755e4d 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -52,6 +52,7 @@
 import static android.window.WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT;
 import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN;
 import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION;
@@ -1131,6 +1132,23 @@
                 }
                 break;
             }
+            case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK: {
+                final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+                if (wc == null || wc.asTask() == null || !wc.isAttached()
+                        || !wc.asTask().isRootTask() || !wc.asTask().mCreatedByOrganizer) {
+                    Slog.e(TAG, "Attempt to remove invalid task: " + wc);
+                    break;
+                }
+                final Task task = wc.asTask();
+                if (task.isVisibleRequested() || task.isVisible()) {
+                    effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                }
+                // Removes its leaves, but not itself.
+                mService.mTaskSupervisor.removeRootTask(task);
+                // Now that the root has no leaves, remove it too. .
+                task.remove(true /* withTransition */, "remove-root-task-through-hierarchyOp");
+                break;
+            }
             case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: {
                 final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
                 if (wc == null || !wc.isAttached()) {
@@ -1380,7 +1398,7 @@
                 break;
             }
             case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: {
-                if (!com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+                if (!com.android.wm.shell.Flags.enableRecentsBookendTransition()) {
                     // Only allow restoring transient order when finishing a transition
                     if (!chain.isFinishing()) break;
                 }
@@ -1416,7 +1434,7 @@
                 final TaskDisplayArea taskDisplayArea = thisTask.getTaskDisplayArea();
                 taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt);
 
-                if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+                if (com.android.wm.shell.Flags.enableRecentsBookendTransition()) {
                     // Because we are in a transient launch transition, the requested visibility of
                     // tasks does not actually change for the transient-hide tasks, but we do want
                     // the restoration of these transient-hide tasks to top to be a part of this
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 26bc09f..30dde54 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -969,14 +969,8 @@
         return canUpdate;
     }
 
+    // TODO(365884835): remove this method with external callers.
     public void stopFreezingActivities() {
-        synchronized (mAtm.mGlobalLock) {
-            int i = mActivities.size();
-            while (i > 0) {
-                i--;
-                mActivities.get(i).stopFreezingScreen(true /* unfreezeNow */, true /* force */);
-            }
-        }
     }
 
     void finishActivities() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index da58470..a8f22ea 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -218,7 +218,6 @@
 import android.util.MergedConfiguration;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -366,7 +365,6 @@
     boolean mPermanentlyHidden; // the window should never be shown again
     // This is a non-system overlay window that is currently force hidden.
     private boolean mForceHideNonSystemOverlayWindow;
-    boolean mAppFreezing;
     boolean mHidden = true;    // Used to determine if to show child windows.
     private boolean mDragResizing;
     private boolean mDragResizingChangeReported = true;
@@ -601,11 +599,6 @@
      */
     int mLastVisibleLayoutRotation = -1;
 
-    /**
-     * How long we last kept the screen frozen.
-     */
-    int mLastFreezeDuration;
-
     /** Is this window now (or just being) removed? */
     boolean mRemoved;
 
@@ -1475,7 +1468,6 @@
 
             consumeInsetsChange();
             onResizeHandled();
-            mWmService.makeWindowFreezingScreenIfNeededLocked(this);
 
             // Reset the drawn state if the window need to redraw for the change, so the transition
             // can wait until it has finished drawing to start.
@@ -1700,7 +1692,7 @@
 
     @Override
     boolean hasContentToDisplay() {
-        if (!mAppFreezing && isDrawn() && (mViewVisibility == View.VISIBLE
+        if (!isDrawn() && (mViewVisibility == View.VISIBLE
                 || (isAnimating(TRANSITION | PARENTS)
                 && !getDisplayContent().mAppTransition.isTransitionSet()))) {
             return true;
@@ -1912,7 +1904,6 @@
      */
     boolean isInteresting() {
         return mActivityRecord != null
-                && (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
                 && mViewVisibility == View.VISIBLE;
     }
 
@@ -2398,12 +2389,12 @@
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                     "Remove %s: mSurfaceControl=%s mAnimatingExit=%b mRemoveOnExit=%b "
                             + "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
-                            + "mDisplayFrozen=%b callers=%s",
+                            + "callers=%s",
                     this, mWinAnimator.mSurfaceControl, mAnimatingExit, mRemoveOnExit,
                     mHasSurface, mWinAnimator.getShown(),
                     isAnimating(TRANSITION | PARENTS),
                     mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
-                    mWmService.mDisplayFrozen, Debug.getCallers(6));
+                    Debug.getCallers(6));
 
             // First, see if we need to run an animation. If we do, we have to hold off on removing the
             // window until the animation is done. If the display is frozen, just remove immediately,
@@ -3266,32 +3257,6 @@
         }
     }
 
-    void onStartFreezingScreen() {
-        mAppFreezing = true;
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = mChildren.get(i);
-            c.onStartFreezingScreen();
-        }
-    }
-
-    boolean onStopFreezingScreen() {
-        boolean unfrozeWindows = false;
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowState c = mChildren.get(i);
-            unfrozeWindows |= c.onStopFreezingScreen();
-        }
-
-        if (!mAppFreezing) {
-            return unfrozeWindows;
-        }
-
-        mAppFreezing = false;
-
-        mLastFreezeDuration = 0;
-        setDisplayLayoutNeeded();
-        return true;
-    }
-
     boolean destroySurface(boolean cleanupOnResume, boolean appStopped) {
         boolean destroyedSomething = false;
 
@@ -4192,16 +4157,6 @@
                     + " mDestroying=" + mDestroying
                     + " mRemoved=" + mRemoved);
         }
-        if (mAppFreezing) {
-            pw.println(prefix + " configOrientationChanging="
-                    + (getLastReportedConfiguration().orientation != getConfiguration().orientation)
-                    + " mAppFreezing=" + mAppFreezing);
-        }
-        if (mLastFreezeDuration != 0) {
-            pw.print(prefix + "mLastFreezeDuration=");
-            TimeUtils.formatDuration(mLastFreezeDuration, pw);
-            pw.println();
-        }
         pw.print(prefix + "mForceSeamlesslyRotate=" + mForceSeamlesslyRotate
                 + " seamlesslyRotate: pending=");
         if (mPendingSeamlessRotate != null) {
@@ -4882,7 +4837,7 @@
             c.updateReportedVisibility(results);
         }
 
-        if (mAppFreezing || mViewVisibility != View.VISIBLE
+        if (mViewVisibility != View.VISIBLE
                 || mAttrs.type == TYPE_APPLICATION_STARTING
                 || mDestroying) {
             return;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 298580e..1d8d867 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -303,8 +303,6 @@
 
         resetDrawState();
 
-        mService.makeWindowFreezingScreenIfNeededLocked(w);
-
         int flags = SurfaceControl.HIDDEN;
         final WindowManager.LayoutParams attrs = w.mAttrs;
 
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 911c686..e1f3f0e 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -357,6 +357,7 @@
     void setTouchpadRightClickZoneEnabled(bool enabled);
     void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
     void setTouchpadSystemGesturesEnabled(bool enabled);
+    void setTouchpadAccelerationEnabled(bool enabled);
     void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
     void setShowTouches(bool enabled);
     void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
@@ -540,6 +541,10 @@
         // True to enable system gestures (three- and four-finger swipes) on touchpads.
         bool touchpadSystemGesturesEnabled{true};
 
+        // True if the speed of the pointer will increase as the user moves
+        // their finger faster on the touchpad.
+        bool touchpadAccelerationEnabled{true};
+
         // True if a pointer icon should be shown for stylus pointers.
         bool stylusPointerIconEnabled{false};
 
@@ -869,6 +874,7 @@
         outConfig->touchpadThreeFingerTapShortcutEnabled =
                 mLocked.touchpadThreeFingerTapShortcutEnabled;
         outConfig->touchpadSystemGesturesEnabled = mLocked.touchpadSystemGesturesEnabled;
+        outConfig->touchpadAccelerationEnabled = mLocked.touchpadAccelerationEnabled;
 
         outConfig->disabledDevices = mLocked.disabledInputDevices;
 
@@ -1666,6 +1672,21 @@
             InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
 }
 
+void NativeInputManager::setTouchpadAccelerationEnabled(bool enabled) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        if (mLocked.touchpadAccelerationEnabled == enabled) {
+            return;
+        }
+
+        mLocked.touchpadAccelerationEnabled = enabled;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
 void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
     bool refresh = false;
 
@@ -2644,6 +2665,13 @@
     im->setTouchpadSystemGesturesEnabled(enabled);
 }
 
+static void nativeSetTouchpadAccelerationEnabled(JNIEnv* env, jobject nativeImplObj,
+                                                 jboolean enabled) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+    im->setTouchpadAccelerationEnabled(enabled);
+}
+
 static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
@@ -3354,6 +3382,7 @@
         {"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
          (void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
         {"setTouchpadSystemGesturesEnabled", "(Z)V", (void*)nativeSetTouchpadSystemGesturesEnabled},
+        {"setTouchpadAccelerationEnabled", "(Z)V", (void*)nativeSetTouchpadAccelerationEnabled},
         {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
         {"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
         {"reloadCalibration", "()V", (void*)nativeReloadCalibration},
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 76d16e1..a81a0b3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -73,9 +73,6 @@
 
 class ActiveAdmin {
 
-    private final int userId;
-    public final boolean isPermissionBased;
-
     private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
     private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin";
     private static final String TAG_DISABLE_CAMERA = "disable-camera";
@@ -364,23 +361,8 @@
     private static final int PROVISIONING_CONTEXT_LENGTH_LIMIT = 1000;
 
     ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
-        this.userId = -1;
         this.info = info;
         this.isParent = isParent;
-        this.isPermissionBased = false;
-    }
-
-    ActiveAdmin(int userId, boolean permissionBased) {
-        if (Flags.activeAdminCleanup()) {
-            throw new UnsupportedOperationException("permission based admin no longer supported");
-        }
-        if (permissionBased == false) {
-            throw new IllegalArgumentException("Can only pass true for permissionBased admin");
-        }
-        this.userId = userId;
-        this.isPermissionBased = permissionBased;
-        this.isParent = false;
-        this.info = null;
     }
 
     ActiveAdmin getParentActiveAdmin() {
@@ -397,16 +379,10 @@
     }
 
     int getUid() {
-        if (isPermissionBased) {
-            return -1;
-        }
         return info.getActivityInfo().applicationInfo.uid;
     }
 
     public UserHandle getUserHandle() {
-        if (isPermissionBased) {
-            return UserHandle.of(userId);
-        }
         return UserHandle.of(UserHandle.getUserId(info.getActivityInfo().applicationInfo.uid));
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index c937e10..89c8b56 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -21,7 +21,6 @@
 import android.annotation.UserIdInt;
 import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyManager;
-import android.app.admin.flags.Flags;
 import android.content.ComponentName;
 import android.os.FileUtils;
 import android.os.PersistableBundle;
@@ -125,24 +124,6 @@
     final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
     final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
 
-    /**
-     * @deprecated Do not use. Policies set by permission holders must go into DevicePolicyEngine.
-     */
-    @Deprecated
-    ActiveAdmin mPermissionBasedAdmin;
-
-    // Create or get the permission-based admin. The permission-based admin will not have a
-    // DeviceAdminInfo or ComponentName.
-    ActiveAdmin createOrGetPermissionBasedAdmin(int userId) {
-        if (Flags.activeAdminCleanup()) {
-            throw new UnsupportedOperationException("permission based admin no longer supported");
-        }
-        if (mPermissionBasedAdmin == null) {
-            mPermissionBasedAdmin = new ActiveAdmin(userId, /* permissionBased= */ true);
-        }
-        return mPermissionBasedAdmin;
-    }
-
     // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
     final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
 
@@ -282,12 +263,6 @@
                 }
             }
 
-            if (!Flags.activeAdminCleanup() && policyData.mPermissionBasedAdmin != null) {
-                out.startTag(null, "permission-based-admin");
-                policyData.mPermissionBasedAdmin.writeToXml(out);
-                out.endTag(null, "permission-based-admin");
-            }
-
             if (policyData.mPasswordOwner >= 0) {
                 out.startTag(null, "password-owner");
                 out.attributeInt(null, "value", policyData.mPasswordOwner);
@@ -495,7 +470,6 @@
             policy.mLockTaskPackages.clear();
             policy.mAdminList.clear();
             policy.mAdminMap.clear();
-            policy.mPermissionBasedAdmin = null;
             policy.mAffiliationIds.clear();
             policy.mOwnerInstalledCaCerts.clear();
             policy.mUserControlDisabledPackages = null;
@@ -523,11 +497,6 @@
                     } catch (RuntimeException e) {
                         Slogf.w(TAG, e, "Failed loading admin %s", name);
                     }
-                } else if (!Flags.activeAdminCleanup() && "permission-based-admin".equals(tag)) {
-
-                    ActiveAdmin ap = new ActiveAdmin(policy.mUserId, /* permissionBased= */ true);
-                    ap.readFromXml(parser, /* overwritePolicies= */ false);
-                    policy.mPermissionBasedAdmin = ap;
                 } else if ("delegation".equals(tag)) {
                     // Parse delegation info.
                     final String delegatePackage = parser.getAttributeValue(null,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e960abd..4c2c858 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3448,8 +3448,8 @@
         EnforcingAdmin enforcingAdmin =
                 EnforcingAdmin.createEnterpriseEnforcingAdmin(
                         admin.info.getComponent(),
-                        admin.getUserHandle().getIdentifier(),
-                        admin);
+                        admin.getUserHandle().getIdentifier()
+                );
         mDevicePolicyEngine.setGlobalPolicy(
                 PolicyDefinition.SECURITY_LOGGING,
                 enforcingAdmin,
@@ -3692,8 +3692,8 @@
         int userId = admin.getUserHandle().getIdentifier();
         EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
                 admin.info.getComponent(),
-                userId,
-                admin);
+                userId
+        );
 
         Integer passwordComplexity = mDevicePolicyEngine.getLocalPolicySetByAdmin(
                 PolicyDefinition.PASSWORD_COMPLEXITY,
@@ -3985,8 +3985,7 @@
             final int N = admins.size();
             for (int i = 0; i < N; i++) {
                 ActiveAdmin admin = admins.get(i);
-                if (((!Flags.activeAdminCleanup() && admin.isPermissionBased)
-                        || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
+                if ((admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
                         && admin.passwordExpirationTimeout > 0L
                         && now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS
                         && admin.passwordExpirationDate > 0L) {
@@ -4167,10 +4166,10 @@
 
         EnforcingAdmin oldAdmin =
                 EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                        outgoingReceiver, userHandle, adminToTransfer);
+                        outgoingReceiver, userHandle);
         EnforcingAdmin newAdmin =
                 EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                        incomingReceiver, userHandle, adminToTransfer);
+                        incomingReceiver, userHandle);
 
         mDevicePolicyEngine.transferPolicies(oldAdmin, newAdmin);
 
@@ -4470,7 +4469,7 @@
         }
         mDevicePolicyEngine.removePoliciesForAdmin(
                 EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                        adminReceiver, userHandle, admin));
+                        adminReceiver, userHandle));
     }
 
     private boolean canSetPasswordQualityOnParent(String packageName, final CallerIdentity caller) {
@@ -4525,10 +4524,8 @@
                     who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
 
             if (Flags.unmanagedModeMigration()) {
-                enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who,
-                        userId,
-                        getActiveAdminForCallerLocked(who,
-                                DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD));
+                getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+                enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId);
             }
             // If setPasswordQuality is called on the parent, ensure that
             // the primary admin does not have password complexity state (this is an
@@ -5584,17 +5581,13 @@
         Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
 
         final ActiveAdmin activeAdmin;
-        if (Flags.activeAdminCleanup()) {
-            if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
-                synchronized (getLockObject()) {
-                    activeAdmin = getActiveAdminUncheckedLocked(
-                            admin.getComponentName(), admin.getUserId());
-                }
-            } else {
-                activeAdmin = null;
+        if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
+            synchronized (getLockObject()) {
+                activeAdmin = getActiveAdminUncheckedLocked(
+                        admin.getComponentName(), admin.getUserId());
             }
         } else {
-            activeAdmin = admin.getActiveAdmin();
+            activeAdmin = null;
         }
 
         // We require the caller to explicitly clear any password quality requirements set
@@ -6331,12 +6324,7 @@
                         caller.getPackageName(),
                         getAffectedUser(parent)
                 );
-                if (Flags.activeAdminCleanup()) {
-                    adminComponent = enforcingAdmin.getComponentName();
-                } else {
-                    ActiveAdmin admin = enforcingAdmin.getActiveAdmin();
-                    adminComponent = admin == null ? null : admin.info.getComponent();
-                }
+                adminComponent = enforcingAdmin.getComponentName();
             } else {
                 ActiveAdmin admin = getActiveAdminOrCheckPermissionForCallerLocked(
                         null,
@@ -7824,19 +7812,9 @@
                     calledByProfileOwnerOnOrgOwnedDevice, calledOnParentInstance);
         }
 
-        int userId;
-        ActiveAdmin admin = null;
-        if (Flags.activeAdminCleanup()) {
-            userId = enforcingAdmin.getUserId();
-            Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser,
-                    enforcingAdmin, userId);
-        } else {
-            admin = enforcingAdmin.getActiveAdmin();
-            userId = admin != null ? admin.getUserHandle().getIdentifier()
-                    : caller.getUserId();
-            Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser, admin,
-                    userId);
-        }
+        int userId = enforcingAdmin.getUserId();
+        Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser,
+                enforcingAdmin, userId);
 
         if (calledByProfileOwnerOnOrgOwnedDevice) {
             // When wipeData is called on the parent instance, it implies wiping the entire device.
@@ -7858,38 +7836,14 @@
 
         final String adminName;
         final ComponentName adminComp;
-        if (Flags.activeAdminCleanup()) {
-            adminComp = enforcingAdmin.getComponentName();
-            adminName = adminComp != null
-                    ? adminComp.flattenToShortString()
-                    : enforcingAdmin.getPackageName();
-            event.setAdmin(enforcingAdmin.getPackageName());
-            // Not including any HSUM handling here because the "else" branch in the "flag off"
-            // case below is unreachable under normal circumstances and for permission-based
-            // callers admin won't be null.
-        } else {
-            if (admin != null) {
-                if (admin.isPermissionBased) {
-                    adminComp = null;
-                    adminName = caller.getPackageName();
-                    event.setAdmin(adminName);
-                } else {
-                    adminComp = admin.info.getComponent();
-                    adminName = adminComp.flattenToShortString();
-                    event.setAdmin(adminComp);
-                }
-            } else {
-                adminComp = null;
-                adminName = mInjector.getPackageManager().getPackagesForUid(caller.getUid())[0];
-                Slogf.i(LOG_TAG, "Logging wipeData() event admin as " + adminName);
-                event.setAdmin(adminName);
-                if (mInjector.userManagerIsHeadlessSystemUserMode()) {
-                    // On headless system user mode, the call is meant to factory reset the whole
-                    // device, otherwise the caller could simply remove the current user.
-                    userId = UserHandle.USER_SYSTEM;
-                }
-            }
-        }
+        adminComp = enforcingAdmin.getComponentName();
+        adminName = adminComp != null
+                ? adminComp.flattenToShortString()
+                : enforcingAdmin.getPackageName();
+        event.setAdmin(enforcingAdmin.getPackageName());
+        // Not including any HSUM handling here because the "else" branch in the "flag off"
+        // case below is unreachable under normal circumstances and for permission-based
+        // callers admin won't be null.
         event.write();
 
         String internalReason = String.format(
@@ -8375,8 +8329,7 @@
         List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(userHandle);
         for (int i = 0; i < admins.size(); i++) {
             ActiveAdmin admin = admins.get(i);
-            if ((!Flags.activeAdminCleanup() && admin.isPermissionBased)
-                    || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
+            if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
                 affectedUserIds.add(admin.getUserHandle().getIdentifier());
                 long timeout = admin.passwordExpirationTimeout;
                 admin.passwordExpirationDate =
@@ -8470,9 +8423,6 @@
      */
     private int getUserIdToWipeForFailedPasswords(ActiveAdmin admin) {
         final int userId = admin.getUserHandle().getIdentifier();
-        if (!Flags.activeAdminCleanup() && admin.isPermissionBased) {
-            return userId;
-        }
         final ComponentName component = admin.info.getComponent();
         return isProfileOwnerOfOrganizationOwnedDevice(component, userId)
                 ? getProfileParentId(userId) : userId;
@@ -10282,8 +10232,7 @@
         setGlobalSettingDeviceOwnerType(DEVICE_OWNER_TYPE_DEFAULT);
 
         mDevicePolicyEngine.removePoliciesForAdmin(
-                EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                        admin.info.getComponent(), userId, admin));
+                EnforcingAdmin.createEnterpriseEnforcingAdmin(admin.info.getComponent(), userId));
     }
 
     private void clearApplicationRestrictions(int userId) {
@@ -10433,8 +10382,7 @@
         setNetworkLoggingActiveInternal(false);
 
         mDevicePolicyEngine.removePoliciesForAdmin(
-                EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                        admin.info.getComponent(), userId, admin));
+                EnforcingAdmin.createEnterpriseEnforcingAdmin(admin.info.getComponent(), userId));
     }
 
     @Override
@@ -16449,8 +16397,7 @@
         if (admin.mPasswordPolicy.quality < minPasswordQuality) {
             return false;
         }
-        return (!Flags.activeAdminCleanup() && admin.isPermissionBased)
-                || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+        return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
     }
 
     @Override
@@ -20918,8 +20865,7 @@
         if (profileOwner != null) {
             EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
                     profileOwner.info.getComponent(),
-                    profileUserId,
-                    profileOwner);
+                    profileUserId);
             mDevicePolicyEngine.setLocalPolicy(
                     PolicyDefinition.PERSONAL_APPS_SUSPENDED,
                     admin,
@@ -23517,27 +23463,6 @@
      *
      * @param callerPackageName The package name  of the calling application.
      * @param adminPolicy The admin policy that should grant holders permission.
-     * @param permission The name of the permission being checked.
-     * @param targetUserId The userId of the user which the caller needs permission to act on.
-     * @throws SecurityException if the caller has not been granted the given permission,
-     * the associated cross-user permission if the caller's user is different to the target user.
-     */
-    private void enforcePermission(String permission, int adminPolicy,
-            String callerPackageName, int targetUserId) throws SecurityException {
-        if (hasAdminPolicy(adminPolicy, callerPackageName)) {
-            return;
-        }
-        enforcePermission(permission, callerPackageName, targetUserId);
-    }
-
-    /**
-     * Checks if the calling process has been granted permission to apply a device policy on a
-     * specific user.
-     * The given permission will be checked along with its associated cross-user permission if it
-     * exists and the target user is different to the calling user.
-     *
-     * @param callerPackageName The package name  of the calling application.
-     * @param adminPolicy The admin policy that should grant holders permission.
      * @param permissions The names of the permissions being checked.
      * @param targetUserId The userId of the user which the caller needs permission to act on.
      * @throws SecurityException if the caller has not been granted the given permission,
@@ -23670,24 +23595,21 @@
             ComponentName component;
             synchronized (getLockObject()) {
                 if (who != null) {
-                    admin = getActiveAdminUncheckedLocked(who, userId);
                     component = who;
                 } else {
                     admin = getDeviceOrProfileOwnerAdminLocked(userId);
                     component = admin.info.getComponent();
                 }
             }
-            return EnforcingAdmin.createEnterpriseEnforcingAdmin(component, userId, admin);
+            return EnforcingAdmin.createEnterpriseEnforcingAdmin(component, userId);
         }
-        // Check for non-DPC active admins.
+        // Check for DA active admins.
         admin = getActiveAdminForCaller(who, caller);
         if (admin != null) {
-            return EnforcingAdmin.createDeviceAdminEnforcingAdmin(admin.info.getComponent(), userId,
-                    admin);
+            return EnforcingAdmin.createDeviceAdminEnforcingAdmin(
+                    admin.info.getComponent(), userId);
         }
-        admin = Flags.activeAdminCleanup()
-                ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
-        return  EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin);
+        return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId);
     }
 
     private EnforcingAdmin getEnforcingAdminForPackage(@Nullable ComponentName who,
@@ -23699,19 +23621,17 @@
                     admin = getActiveAdminUncheckedLocked(who, userId);
                 }
                 if (admin != null) {
-                    return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId, admin);
+                    return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId);
                 }
             } else {
-                // Check for non-DPC active admins.
+                // Check for DA active admins.
                 admin = getActiveAdminUncheckedLocked(who, userId);
                 if (admin != null) {
-                    return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin);
+                    return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId);
                 }
             }
         }
-        admin = Flags.activeAdminCleanup()
-                ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
-        return  EnforcingAdmin.createEnforcingAdmin(packageName, userId, admin);
+        return EnforcingAdmin.createEnforcingAdmin(packageName, userId);
     }
 
     private int getAffectedUser(boolean calledOnParent) {
@@ -24427,9 +24347,7 @@
                     && admin.getParentActiveAdmin().disableScreenCapture))) {
 
                 EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                        admin.info.getComponent(),
-                        admin.getUserHandle().getIdentifier(),
-                        admin);
+                        admin.info.getComponent(), admin.getUserHandle().getIdentifier());
                 mDevicePolicyEngine.setGlobalPolicy(
                         PolicyDefinition.SCREEN_CAPTURE_DISABLED,
                         enforcingAdmin,
@@ -24442,8 +24360,7 @@
                 if (profileOwner != null && profileOwner.disableScreenCapture) {
                     EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
                             profileOwner.info.getComponent(),
-                            profileOwner.getUserHandle().getIdentifier(),
-                            profileOwner);
+                            profileOwner.getUserHandle().getIdentifier());
                     mDevicePolicyEngine.setLocalPolicy(
                             PolicyDefinition.SCREEN_CAPTURE_DISABLED,
                             enforcingAdmin,
@@ -24485,10 +24402,7 @@
     private void setLockTaskPolicyInPolicyEngine(
             ActiveAdmin admin, int userId, List<String> packages, int features) {
         EnforcingAdmin enforcingAdmin =
-                EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                        admin.info.getComponent(),
-                        userId,
-                        admin);
+                EnforcingAdmin.createEnterpriseEnforcingAdmin(admin.info.getComponent(), userId);
         mDevicePolicyEngine.setLocalPolicy(
                 PolicyDefinition.LOCK_TASK,
                 enforcingAdmin,
@@ -24503,9 +24417,7 @@
                 ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
                 if (admin != null) {
                     EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                            admin.info.getComponent(),
-                            admin.getUserHandle().getIdentifier(),
-                            admin);
+                            admin.info.getComponent(), admin.getUserHandle().getIdentifier());
                     if (admin.permittedInputMethods != null) {
                         mDevicePolicyEngine.setLocalPolicy(
                                 PolicyDefinition.PERMITTED_INPUT_METHODS,
@@ -24536,9 +24448,7 @@
                 ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
                 if (admin != null) {
                     EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                            admin.info.getComponent(),
-                            admin.getUserHandle().getIdentifier(),
-                            admin);
+                            admin.info.getComponent(), admin.getUserHandle().getIdentifier());
                     for (String accountType : admin.accountTypesWithManagementDisabled) {
                         mDevicePolicyEngine.setLocalPolicy(
                                 PolicyDefinition.ACCOUNT_MANAGEMENT_DISABLED(accountType),
@@ -24569,9 +24479,7 @@
                 ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
                 if (admin != null && admin.protectedPackages != null) {
                     EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                            admin.info.getComponent(),
-                            admin.getUserHandle().getIdentifier(),
-                            admin);
+                            admin.info.getComponent(), admin.getUserHandle().getIdentifier());
                     if (isDeviceOwner(admin)) {
                         mDevicePolicyEngine.setGlobalPolicy(
                                 PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
@@ -24599,10 +24507,8 @@
                 if (admin == null) continue;
                 ComponentName adminComponent = admin.info.getComponent();
                 int userId = userInfo.id;
-                EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                        adminComponent,
-                        userId,
-                        admin);
+                EnforcingAdmin enforcingAdmin =
+                        EnforcingAdmin.createEnterpriseEnforcingAdmin(adminComponent, userId);
                 int ownerType;
                 if (isDeviceOwner(admin)) {
                     ownerType = OWNER_TYPE_DEVICE_OWNER;
@@ -24635,9 +24541,7 @@
                 ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
                 if (admin == null) continue;
                 EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                        admin.info.getComponent(),
-                        userInfo.id,
-                        admin);
+                        admin.info.getComponent(), userInfo.id);
 
                 runner.accept(admin, enforcingAdmin);
             }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 5a0b079..aca3315 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -23,7 +23,6 @@
 import android.app.admin.DpcAuthority;
 import android.app.admin.RoleAuthority;
 import android.app.admin.UnknownAuthority;
-import android.app.admin.flags.Flags;
 import android.content.ComponentName;
 import android.os.UserHandle;
 
@@ -80,36 +79,24 @@
     private final int mUserId;
     private final boolean mIsRoleAuthority;
     private final boolean mIsSystemAuthority;
-    private final ActiveAdmin mActiveAdmin;
 
-    static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId,
-            ActiveAdmin admin) {
+    static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId) {
         Objects.requireNonNull(packageName);
-        return new EnforcingAdmin(packageName, userId, admin);
+        return new EnforcingAdmin(packageName, userId);
     }
 
     static EnforcingAdmin createEnterpriseEnforcingAdmin(
             @NonNull ComponentName componentName, int userId) {
         Objects.requireNonNull(componentName);
         return new EnforcingAdmin(
-                componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId,
-                /* activeAdmin=*/ null);
+                componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId);
     }
 
-    static EnforcingAdmin createEnterpriseEnforcingAdmin(
-            @NonNull ComponentName componentName, int userId, ActiveAdmin activeAdmin) {
-        Objects.requireNonNull(componentName);
-        return new EnforcingAdmin(
-                componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId,
-                activeAdmin);
-    }
-
-    static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId,
-            ActiveAdmin activeAdmin) {
+    static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId) {
         Objects.requireNonNull(componentName);
         return new EnforcingAdmin(
                 componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY),
-                userId, activeAdmin);
+                userId);
     }
 
     static EnforcingAdmin createSystemEnforcingAdmin(@NonNull String systemEntity) {
@@ -124,24 +111,20 @@
         if (DpcAuthority.DPC_AUTHORITY.equals(authority)) {
             return new EnforcingAdmin(
                     admin.getPackageName(), admin.getComponentName(),
-                    Set.of(DPC_AUTHORITY), admin.getUserHandle().getIdentifier(),
-                    /* activeAdmin = */ null);
+                    Set.of(DPC_AUTHORITY), admin.getUserHandle().getIdentifier());
         } else if (DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY.equals(authority)) {
             return new EnforcingAdmin(
                     admin.getPackageName(), admin.getComponentName(),
-                    Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier(),
-                    /* activeAdmin = */ null);
+                    Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier());
         } else if (authority instanceof RoleAuthority roleAuthority) {
             return new EnforcingAdmin(
                     admin.getPackageName(), admin.getComponentName(),
                     Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier(),
-                    /* activeAdmin = */ null,
                     /* isRoleAuthority = */ true);
         }
         // TODO(b/324899199): Consider supporting android.app.admin.SystemAuthority.
         return new EnforcingAdmin(admin.getPackageName(), admin.getComponentName(),
-                Set.of(), admin.getUserHandle().getIdentifier(),
-                /* activeAdmin = */ null);
+                Set.of(), admin.getUserHandle().getIdentifier());
     }
 
     static String getRoleAuthorityOf(String roleName) {
@@ -167,7 +150,7 @@
 
     private EnforcingAdmin(
             String packageName, @Nullable ComponentName componentName, Set<String> authorities,
-            int userId, @Nullable ActiveAdmin activeAdmin) {
+            int userId) {
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(authorities);
 
@@ -179,10 +162,9 @@
         mComponentName = componentName;
         mAuthorities = new HashSet<>(authorities);
         mUserId = userId;
-        mActiveAdmin = activeAdmin;
     }
 
-    private EnforcingAdmin(String packageName, int userId, ActiveAdmin activeAdmin) {
+    private EnforcingAdmin(String packageName, int userId) {
         Objects.requireNonNull(packageName);
 
         // Only role authorities use this constructor.
@@ -194,7 +176,6 @@
         mComponentName = null;
         // authorities will be loaded when needed
         mAuthorities = null;
-        mActiveAdmin = activeAdmin;
     }
 
     /** Constructor for System authorities. */
@@ -210,12 +191,11 @@
         mUserId = UserHandle.USER_SYSTEM;
         mComponentName = null;
         mAuthorities = getSystemAuthority(systemEntity);
-        mActiveAdmin = null;
     }
 
     private EnforcingAdmin(
             String packageName, @Nullable ComponentName componentName, Set<String> authorities,
-            int userId, @Nullable ActiveAdmin activeAdmin, boolean isRoleAuthority) {
+            int userId, boolean isRoleAuthority) {
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(authorities);
 
@@ -226,7 +206,6 @@
         mComponentName = componentName;
         mAuthorities = new HashSet<>(authorities);
         mUserId = userId;
-        mActiveAdmin = activeAdmin;
     }
 
     private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) {
@@ -295,14 +274,6 @@
     }
 
     @Nullable
-    public ActiveAdmin getActiveAdmin() {
-        if (Flags.activeAdminCleanup()) {
-            throw new UnsupportedOperationException("getActiveAdmin() no longer supported");
-        }
-        return mActiveAdmin;
-    }
-
-    @Nullable
     ComponentName getComponentName() {
         return mComponentName;
     }
@@ -419,7 +390,7 @@
                 return null;
             }
             // TODO(b/281697976): load active admin
-            return new EnforcingAdmin(packageName, userId, null);
+            return new EnforcingAdmin(packageName, userId);
         } else if (isSystemAuthority) {
             if (systemEntity == null) {
                 Slogf.wtf(TAG, "Error parsing EnforcingAdmin with SystemAuthority, "
@@ -439,7 +410,7 @@
                     ? null :  new ComponentName(packageName, className);
             Set<String> authorities = Set.of(authoritiesStr.split(ATTR_AUTHORITIES_SEPARATOR));
             // TODO(b/281697976): load active admin
-            return new EnforcingAdmin(packageName, componentName, authorities, userId, null);
+            return new EnforcingAdmin(packageName, componentName, authorities, userId);
         }
     }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fadab1f..25e9f8a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1527,6 +1527,8 @@
         boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
                 false);
 
+        boolean isDesktop = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC);
+
         boolean isWatch = RoSystemFeatures.hasFeatureWatch(context);
 
         boolean isArc = context.getPackageManager().hasSystemFeature(
@@ -1656,7 +1658,7 @@
                 t.traceEnd();
             }
 
-            if (!isTv) {
+            if (!isTv && !isDesktop) {
                 t.traceBegin("StartVibratorManagerService");
                 mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
                 t.traceEnd();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 02e5470..e8b28ac 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -23,6 +23,7 @@
 import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
 import static android.Manifest.permission.MANAGE_DISPLAYS;
 import static android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE;
+import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
 import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
@@ -2123,16 +2124,14 @@
         }
     }
 
-    /**
-     * Tests that there is a display change notification if the frame rate override
-     * list is updated.
-     */
     @Test
-    public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() {
+    public void test_displayChangedNotified_displayInfoFramerateOverridden() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
+        when(mMockFlags.isFramerateOverrideTriggersRrCallbacksEnabled()).thenReturn(false);
+
         registerDefaultDisplays(displayManager);
         displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
 
@@ -2148,6 +2147,35 @@
         waitForIdleHandler(displayManager.getDisplayHandler());
         assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
         callback.clear();
+    }
+
+    /**
+     * Tests that there is a display change notification if the frame rate override
+     * list is updated.
+     */
+    @Test
+    public void test_refreshRateChangedNotified_displayInfoFramerateOverridden() {
+        when(mMockFlags.isFramerateOverrideTriggersRrCallbacksEnabled()).thenReturn(true);
+
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+                displayManagerBinderService, displayDevice);
+
+        int myUid = Process.myUid();
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
+                });
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
+        callback.clear();
 
         updateFrameRateOverride(displayManager, displayDevice,
                 new DisplayEventReceiver.FrameRateOverride[]{
@@ -2155,7 +2183,7 @@
                         new DisplayEventReceiver.FrameRateOverride(1234, 30f),
                 });
         waitForIdleHandler(displayManager.getDisplayHandler());
-        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
+        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
 
         updateFrameRateOverride(displayManager, displayDevice,
                 new DisplayEventReceiver.FrameRateOverride[]{
@@ -2164,7 +2192,7 @@
                         new DisplayEventReceiver.FrameRateOverride(5678, 30f),
                 });
         waitForIdleHandler(displayManager.getDisplayHandler());
-        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
         callback.clear();
 
         updateFrameRateOverride(displayManager, displayDevice,
@@ -2173,7 +2201,7 @@
                         new DisplayEventReceiver.FrameRateOverride(5678, 30f),
                 });
         waitForIdleHandler(displayManager.getDisplayHandler());
-        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
         callback.clear();
 
         updateFrameRateOverride(displayManager, displayDevice,
@@ -2181,7 +2209,7 @@
                         new DisplayEventReceiver.FrameRateOverride(5678, 30f),
                 });
         waitForIdleHandler(displayManager.getDisplayHandler());
-        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
+        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
     }
 
     /**
@@ -2317,6 +2345,29 @@
         callback.clear();
     }
 
+    @Test
+    public void test_doesNotNotifyRefreshRateChanged_whenAppInBackground() {
+        when(mMockFlags.isRefreshRateEventForForegroundAppsEnabled()).thenReturn(true);
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        displayManager.windowManagerAndInputReady();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+                displayManagerBinderService, displayDevice);
+
+        when(mMockActivityManagerInternal.getUidProcessState(Process.myUid()))
+                .thenReturn(PROCESS_STATE_TRANSIENT_BACKGROUND);
+        updateRenderFrameRate(displayManager, displayDevice, 30f);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        assertEquals(0, callback.receivedEvents().size());
+        callback.clear();
+    }
+
     /**
      * Tests that the DisplayInfo is updated correctly with a render frame rate
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
index e8e1dac..8ce05e2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -40,6 +40,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
@@ -329,6 +330,7 @@
         }
     }
 
+    @Ignore("b/387389929")
     @Test
     public void recordScreenPolicy_otherTransitions_doesNotReset() {
         DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java
new file mode 100644
index 0000000..acce813
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java
@@ -0,0 +1,242 @@
+/*
+ * 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;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.testutils.MockitoUtilsKt.eq;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertNotNull;
+import static org.testng.AssertJUnit.assertNull;
+
+import android.content.Context;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Test cases for {@link AutoclickController}. */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class AutoclickControllerTest {
+
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Rule
+    public TestableContext mTestableContext =
+            new TestableContext(getInstrumentation().getContext());
+
+    private TestableLooper mTestableLooper;
+    @Mock private AccessibilityTraceManager mMockTrace;
+    @Mock private WindowManager mMockWindowManager;
+    private AutoclickController mController;
+
+    @Before
+    public void setUp() {
+        mTestableLooper = TestableLooper.get(this);
+        mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager);
+        mController =
+                new AutoclickController(mTestableContext, mTestableContext.getUserId(), mMockTrace);
+    }
+
+    @After
+    public void tearDown() {
+        mTestableLooper.processAllMessages();
+    }
+
+    @Test
+    public void onMotionEvent_lazyInitClickScheduler() {
+        assertNull(mController.mClickScheduler);
+
+        injectFakeMouseActionDownEvent();
+
+        assertNotNull(mController.mClickScheduler);
+    }
+
+    @Test
+    public void onMotionEvent_nonMouseSource_notInitClickScheduler() {
+        assertNull(mController.mClickScheduler);
+
+        injectFakeNonMouseActionDownEvent();
+
+        assertNull(mController.mClickScheduler);
+    }
+
+    @Test
+    public void onMotionEvent_lazyInitAutoclickSettingsObserver() {
+        assertNull(mController.mAutoclickSettingsObserver);
+
+        injectFakeMouseActionDownEvent();
+
+        assertNotNull(mController.mAutoclickSettingsObserver);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorScheduler() {
+        assertNull(mController.mAutoclickIndicatorScheduler);
+
+        injectFakeMouseActionDownEvent();
+
+        assertNotNull(mController.mAutoclickIndicatorScheduler);
+    }
+
+    @Test
+    @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void onMotionEvent_flagOff_notInitAutoclickIndicatorScheduler() {
+        assertNull(mController.mAutoclickIndicatorScheduler);
+
+        injectFakeMouseActionDownEvent();
+
+        assertNull(mController.mAutoclickIndicatorScheduler);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorView() {
+        assertNull(mController.mAutoclickIndicatorView);
+
+        injectFakeMouseActionDownEvent();
+
+        assertNotNull(mController.mAutoclickIndicatorView);
+    }
+
+    @Test
+    @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void onMotionEvent_flagOff_notInitAutoclickIndicatorView() {
+        assertNull(mController.mAutoclickIndicatorView);
+
+        injectFakeMouseActionDownEvent();
+
+        assertNull(mController.mAutoclickIndicatorView);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void onMotionEvent_flagOn_addAutoclickIndicatorViewToWindowManager() {
+        injectFakeMouseActionDownEvent();
+
+        verify(mMockWindowManager).addView(eq(mController.mAutoclickIndicatorView), any());
+    }
+
+    @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void onDestroy_flagOn_removeAutoclickIndicatorViewToWindowManager() {
+        injectFakeMouseActionDownEvent();
+
+        mController.onDestroy();
+
+        verify(mMockWindowManager).removeView(mController.mAutoclickIndicatorView);
+    }
+
+    @Test
+    public void onMotionEvent_initClickSchedulerDelayFromSetting() {
+        injectFakeMouseActionDownEvent();
+
+        int delay =
+                Settings.Secure.getIntForUser(
+                        mTestableContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
+                        AccessibilityManager.AUTOCLICK_DELAY_DEFAULT,
+                        mTestableContext.getUserId());
+        assertEquals(delay, mController.mClickScheduler.getDelayForTesting());
+    }
+
+    @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void onMotionEvent_flagOn_initCursorAreaSizeFromSetting() {
+        injectFakeMouseActionDownEvent();
+
+        int size =
+                Settings.Secure.getIntForUser(
+                        mTestableContext.getContentResolver(),
+                        Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
+                        AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT,
+                        mTestableContext.getUserId());
+        assertEquals(size, mController.mAutoclickIndicatorView.getRadiusForTesting());
+    }
+
+    @Test
+    public void onDestroy_clearClickScheduler() {
+        injectFakeMouseActionDownEvent();
+
+        mController.onDestroy();
+
+        assertNull(mController.mClickScheduler);
+    }
+
+    @Test
+    public void onDestroy_clearAutoclickSettingsObserver() {
+        injectFakeMouseActionDownEvent();
+
+        mController.onDestroy();
+
+        assertNull(mController.mAutoclickSettingsObserver);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+    public void onDestroy_flagOn_clearAutoclickIndicatorScheduler() {
+        injectFakeMouseActionDownEvent();
+
+        mController.onDestroy();
+
+        assertNull(mController.mAutoclickIndicatorScheduler);
+    }
+
+    private void injectFakeMouseActionDownEvent() {
+        MotionEvent event = getFakeMotionDownEvent();
+        event.setSource(InputDevice.SOURCE_MOUSE);
+        mController.onMotionEvent(event, event, /* policyFlags= */ 0);
+    }
+
+    private void injectFakeNonMouseActionDownEvent() {
+        MotionEvent event = getFakeMotionDownEvent();
+        event.setSource(InputDevice.SOURCE_KEYBOARD);
+        mController.onMotionEvent(event, event, /* policyFlags= */ 0);
+    }
+
+    private MotionEvent getFakeMotionDownEvent() {
+        return MotionEvent.obtain(
+                /* downTime= */ 0,
+                /* eventTime= */ 0,
+                /* action= */ MotionEvent.ACTION_DOWN,
+                /* x= */ 0,
+                /* y= */ 0,
+                /* metaState= */ 0);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 39206dc..3b32701 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -1612,7 +1612,7 @@
                 );
     }
 
-    public void testThrottling() {
+    public void disabled_testThrottling() {
         final ShortcutInfo si1 = makeShortcut("shortcut1");
 
         assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1685,7 +1685,7 @@
         assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());
     }
 
-    public void testThrottling_rewind() {
+    public void disabled_testThrottling_rewind() {
         final ShortcutInfo si1 = makeShortcut("shortcut1");
 
         assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1715,7 +1715,7 @@
         assertEquals(3, mManager.getRemainingCallCount());
     }
 
-    public void testThrottling_perPackage() {
+    public void disabled_testThrottling_perPackage() {
         final ShortcutInfo si1 = makeShortcut("shortcut1");
 
         assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1847,7 +1847,7 @@
         });
     }
 
-    public void testThrottling_foreground() throws Exception {
+    public void disabled_testThrottling_foreground() throws Exception {
         prepareCrossProfileDataSet();
 
         dumpsysOnLogcat("Before save & load");
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
index aad06c6..81ebf86 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
@@ -171,11 +171,12 @@
                 .haveRanksInOrder("ms1");
     }
 
-    public void testSetDynamicShortcuts_withManifestShortcuts() {
-        runTestWithManifestShortcuts(() -> testSetDynamicShortcuts_noManifestShortcuts());
+    public void disabled_testSetDynamicShortcuts_withManifestShortcuts() {
+        runTestWithManifestShortcuts(() ->
+                disabled_testAddDynamicShortcuts_noManifestShortcuts());
     }
 
-    public void testAddDynamicShortcuts_noManifestShortcuts() {
+    public void disabled_testAddDynamicShortcuts_noManifestShortcuts() {
         mManager.addDynamicShortcuts(list(
                 shortcut("s1", A1)
         ));
@@ -264,8 +265,8 @@
                 .haveIds("s1", "s2", "s4", "s5", "s10");
     }
 
-    public void testAddDynamicShortcuts_withManifestShortcuts() {
-        runTestWithManifestShortcuts(() -> testAddDynamicShortcuts_noManifestShortcuts());
+    public void disabled_testAddDynamicShortcuts_withManifestShortcuts() {
+        runTestWithManifestShortcuts(() -> disabled_testAddDynamicShortcuts_noManifestShortcuts());
     }
 
     public void testUpdateShortcuts_noManifestShortcuts() {
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 148c968..263ada8 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -69,6 +69,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
 import android.service.quicksettings.TileService;
 import android.testing.TestableContext;
 
@@ -79,6 +80,7 @@
 import com.android.server.LocalServices;
 import com.android.server.policy.GlobalActionsProvider;
 import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.systemui.shared.Flags;
 
 import libcore.junit.util.compat.CoreCompatChangeRule;
 
@@ -105,6 +107,7 @@
             TEST_SERVICE);
     private static final CharSequence APP_NAME = "AppName";
     private static final CharSequence TILE_LABEL = "Tile label";
+    private static final int SECONDARY_DISPLAY_ID = 2;
 
     @Rule
     public final TestableContext mContext =
@@ -749,6 +752,29 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+    public void testDisableForAllDisplays() throws Exception {
+        int user1Id = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(user1Id);
+
+        mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
+
+        int expectedFlags = DISABLE_MASK & DISABLE_BACK;
+        String pkg = mContext.getPackageName();
+
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
+
+        // disable
+        mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
+
+        verify(mMockStatusBar).disable(0, expectedFlags, 0);
+        verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, expectedFlags, 0);
+    }
+
+    @Test
     public void testSetHomeDisabled() throws Exception {
         int expectedFlags = DISABLE_MASK & DISABLE_HOME;
         String pkg = mContext.getPackageName();
@@ -851,6 +877,29 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+    public void testDisable2ForAllDisplays() throws Exception {
+        int user1Id = 0;
+        mockUidCheck();
+        mockCurrentUserCheck(user1Id);
+
+        mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
+
+        int expectedFlags = DISABLE2_MASK & DISABLE2_NOTIFICATION_SHADE;
+        String pkg = mContext.getPackageName();
+
+        // before disabling
+        assertEquals(DISABLE_NONE,
+                mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
+
+        // disable
+        mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
+
+        verify(mMockStatusBar).disable(0, 0, expectedFlags);
+        verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, 0, expectedFlags);
+    }
+
+    @Test
     public void testSetQuickSettingsDisabled2() throws Exception {
         int expectedFlags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS;
         String pkg = mContext.getPackageName();
@@ -1092,6 +1141,7 @@
         // disable
         mStatusBarManagerService.disableForUser(expectedUser1Flags, mMockStatusBar, pkg, user1Id);
         mStatusBarManagerService.disableForUser(expectedUser2Flags, mMockStatusBar, pkg, user2Id);
+
         // check that right flag is disabled
         assertEquals(expectedUser1Flags,
                 mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 1df8e3d..d1afa38 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -17,15 +17,12 @@
 
 import static android.os.UserHandle.USER_ALL;
 import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_TYPE;
 import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
 import static android.service.notification.Adjustment.TYPE_NEWS;
-import static android.service.notification.Adjustment.TYPE_OTHER;
 import static android.service.notification.Adjustment.TYPE_PROMOTION;
-import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
-import static android.service.notification.Flags.notificationClassification;
 
 import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENTS;
-import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -160,17 +157,6 @@
         mAssistants.readXml(parser, mNm::canUseManagedServices, false, USER_ALL);
     }
 
-    private void setDefaultAllowedAdjustmentKeyTypes(NotificationAssistants assistants) {
-        assistants.setAssistantAdjustmentKeyTypeState(TYPE_OTHER, false);
-        assistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
-        assistants.setAssistantAdjustmentKeyTypeState(TYPE_SOCIAL_MEDIA, false);
-        assistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false);
-        assistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, false);
-
-        for (int type : DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES) {
-            assistants.setAssistantAdjustmentKeyTypeState(type, true);
-        }
-    }
 
     @Before
     public void setUp() throws Exception {
@@ -180,10 +166,8 @@
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.string.config_defaultAssistantAccessComponent,
                 mCn.flattenToString());
+        mNm.mDefaultUnsupportedAdjustments = new String[] {};
         mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
-        if (notificationClassification()) {
-            setDefaultAllowedAdjustmentKeyTypes(mAssistants);
-        }
         when(mNm.getBinderService()).thenReturn(mINm);
         mContext.ensureTestableResources();
 
@@ -678,7 +662,7 @@
         mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
         assertThat(mAssistants.getAllowedAssistantAdjustments())
                 .doesNotContain(Adjustment.KEY_RANKING_SCORE);
-        assertThat(mAssistants.getAllowedAssistantAdjustments()).contains(Adjustment.KEY_TYPE);
+        assertThat(mAssistants.getAllowedAssistantAdjustments()).contains(KEY_TYPE);
     }
 
     @Test
@@ -727,7 +711,7 @@
         mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true);
 
         assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
-                .containsExactly(TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION);
+                .containsExactlyElementsIn(List.of(TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION));
     }
 
     @Test
@@ -748,7 +732,7 @@
         writeXmlAndReload(USER_ALL);
 
         assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList()
-                .containsExactly(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION);
+                .containsExactlyElementsIn(List.of(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION));
     }
 
     @Test
@@ -765,168 +749,98 @@
     @Test
     @EnableFlags({android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION,
             android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI})
-    public void testGetPackagesWithKeyTypeAdjustmentSettings() throws Exception {
+    public void testGetTypeAdjustmentDeniedPackages() throws Exception {
         String pkg = "my.package";
         String pkg2 = "my.package.2";
-        setDefaultAllowedAdjustmentKeyTypes(mAssistants);
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue();
-        assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings()).isEmpty();
+        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg)).isTrue();
+        assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
 
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, true);
-        assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings())
+        mAssistants.setTypeAdjustmentForPackageState(pkg, true);
+        assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
+        mAssistants.setTypeAdjustmentForPackageState(pkg, false);
+        assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
                 .containsExactly(pkg);
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false);
-        assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings())
+        mAssistants.setTypeAdjustmentForPackageState(pkg2, true);
+      assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
                 .containsExactly(pkg);
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg2, TYPE_NEWS, true);
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg2, TYPE_PROMOTION, false);
-        assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings())
+        mAssistants.setTypeAdjustmentForPackageState(pkg2, false);
+        assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
                 .containsExactly(pkg, pkg2);
     }
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
-    public void testSetAssistantAdjustmentKeyTypeStateForPackage_usesGlobalDefault() {
-        String pkg = "my.package";
-        setDefaultAllowedAdjustmentKeyTypes(mAssistants);
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue();
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isFalse();
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
-                .containsExactlyElementsIn(DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES);
-    }
+    public void testSetTypeAdjustmentForPackageState_allowsAndDenies() {
+        // Given that a package is allowed to have its type adjusted,
+        String allowedPackage = "allowed.package";
+        assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
+        mAssistants.setTypeAdjustmentForPackageState(allowedPackage, true);
 
-    @Test
-    @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
-    public void testSetAssistantAdjustmentKeyTypeStateForPackage_allowsAndDenies() {
-        setDefaultAllowedAdjustmentKeyTypes(mAssistants);
-        // Given that a package is set to have a type adjustment allowed,
-        String pkg = "my.package";
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_NEWS, true);
-
-        // The newly set state is the combination of the global default and the newly set type.
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
-                .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue();
+        assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
+        assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage));
 
         // Set type adjustment disallowed for this package
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_NEWS, false);
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false);
+        mAssistants.setTypeAdjustmentForPackageState(allowedPackage, false);
 
         // Then the package is marked as denied
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).isEmpty();
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isFalse();
+        assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
+                .containsExactly(allowedPackage);
+        assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage));
 
         // Set type adjustment allowed again
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_NEWS, true);
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, true);
+        mAssistants.setTypeAdjustmentForPackageState(allowedPackage, true);
 
         // Then the package is marked as allowed again
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
-                .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue();
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue();
-
-        // Set type adjustment promotions false,
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false);
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
-                .containsExactly(TYPE_NEWS);
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue();
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isFalse();
+        assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).isEmpty();
+        assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPackage));
     }
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
-    public void testSetAssistantAdjustmentKeyTypeStateForPackage_allowsMultiplePkgs() {
-        setDefaultAllowedAdjustmentKeyTypes(mAssistants);
-        // Given packages allowed to have their type adjusted to  TYPE_NEWS,
-        String allowedPkg1 = "allowed.Pkg1";
-        String allowedPkg2 = "allowed.Pkg2";
-        String allowedPkg3 = "allowed.Pkg3";
-        // Set type adjustment allowed for these packages
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg1, TYPE_NEWS, true);
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, true);
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_NEWS, true);
+    public void testSetAssistantAdjustmentKeyTypeStateForPackage_deniesMultiple() {
+        // Given packages not allowed to have their type adjusted,
+        String deniedPkg1 = "denied.Pkg1";
+        String deniedPkg2 = "denied.Pkg2";
+        String deniedPkg3 = "denied.Pkg3";
+        // Set type adjustment disallowed for these packages
+        mAssistants.setTypeAdjustmentForPackageState(deniedPkg1, false);
+        mAssistants.setTypeAdjustmentForPackageState(deniedPkg2, false);
+        mAssistants.setTypeAdjustmentForPackageState(deniedPkg3, false);
 
-        // The newly set state is the combination of the global default and the newly set type.
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg1)).asList()
-                .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg2)).asList()
-                .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg3)).asList()
-                .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg1, TYPE_NEWS)).isTrue();
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg2, TYPE_NEWS)).isTrue();
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg3, TYPE_NEWS)).isTrue();
+        // Then the packages are marked as denied
+        assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
+                .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg2, deniedPkg3));
+        assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg1));
+        assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg2));
+        assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg3));
 
-        // And when we deny some of them,
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, false);
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_PROMOTION,
-                false);
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_PROMOTION,
-                false);
+        // And when we re-allow one of them,
+        mAssistants.setTypeAdjustmentForPackageState(deniedPkg2, true);
 
-        // Then the rest of the original packages are still marked as allowed.
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg1)).asList()
-                .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg2)).isEmpty();
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg3)).asList()
-                .containsExactly(TYPE_NEWS);
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg1, TYPE_NEWS)).isTrue();
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg2, TYPE_NEWS)).isFalse();
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(allowedPkg3, TYPE_NEWS)).isTrue();
+        // Then the rest of the original packages are still marked as denied.
+        assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
+                .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg3));
+        assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg1));
+        assertTrue(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg2));
+        assertFalse(mAssistants.isTypeAdjustmentAllowedForPackage(deniedPkg3));
     }
 
     @Test
     @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
     public void testSetAssistantAdjustmentKeyTypeStateForPackage_readWriteXml() throws Exception {
-        setDefaultAllowedAdjustmentKeyTypes(mAssistants);
         mAssistants.loadDefaultsFromConfig(true);
         String deniedPkg1 = "denied.Pkg1";
         String allowedPkg2 = "allowed.Pkg2";
-        String allowedPkg3 = "allowed.Pkg3";
+        String deniedPkg3 = "denied.Pkg3";
         // Set type adjustment disallowed or allowed for these packages
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(deniedPkg1, TYPE_PROMOTION, false);
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg2, TYPE_NEWS, true);
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_NEWS, true);
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(allowedPkg3, TYPE_SOCIAL_MEDIA,
-                true);
+        mAssistants.setTypeAdjustmentForPackageState(deniedPkg1, false);
+        mAssistants.setTypeAdjustmentForPackageState(allowedPkg2, true);
+        mAssistants.setTypeAdjustmentForPackageState(deniedPkg3, false);
 
         writeXmlAndReload(USER_ALL);
 
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(deniedPkg1)).isEmpty();
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg2)).asList()
-                .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(allowedPkg3)).asList()
-                .containsExactly(TYPE_NEWS, TYPE_SOCIAL_MEDIA, TYPE_PROMOTION);
-    }
-
-    @Test
-    @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
-    public void testSetAssistantAdjustmentKeyTypeStateForPackage_noGlobalImpact() throws Exception {
-        setDefaultAllowedAdjustmentKeyTypes(mAssistants);
-        // When the global state is changed,
-        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true);
-
-        // The package state reflects the global state.
-        String pkg = "my.package";
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue();
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue();
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
-                .containsExactly(TYPE_NEWS, TYPE_PROMOTION);
-
-        // Once the package specific state is modified,
-        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_SOCIAL_MEDIA, true);
-
-        // The package specific state combines the global state with those modifications
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_SOCIAL_MEDIA)).isTrue();
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
-                .containsExactly(TYPE_NEWS, TYPE_PROMOTION, TYPE_SOCIAL_MEDIA);
-
-        // And further changes to the global state are ignored.
-        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false);
-        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_NEWS)).isTrue();
-        assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
-                .containsExactly(TYPE_NEWS, TYPE_PROMOTION, TYPE_SOCIAL_MEDIA);
+        assertThat(mAssistants.getTypeAdjustmentDeniedPackages()).asList()
+                .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg3));
     }
 
     @Test
@@ -944,7 +858,7 @@
                 mAssistants.new ManagedServiceInfo(null, mCn, userId, false, null, 35, 2345256);
 
         // Ensure bundling is enabled
-        mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_TYPE, true);
+        mAssistants.setAdjustmentTypeSupportedState(info, KEY_TYPE, true);
         // Enable these specific bundle types
         mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
         mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true);
@@ -978,7 +892,7 @@
                 .isEqualTo(NotificationProtoEnums.TYPE_CONTENT_RECOMMENDATION);
 
         // Disable the top-level bundling setting
-        mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_TYPE, false);
+        mAssistants.setAdjustmentTypeSupportedState(info, KEY_TYPE, false);
         // Enable these specific bundle types
         mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, true);
         mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false);
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 fdb6a68..4330226 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -369,9 +369,6 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -387,6 +384,9 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Consumer;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4.class)
 @RunWithLooper
@@ -790,6 +790,8 @@
         TestableResources tr = mContext.getOrCreateTestableResources();
         tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName,
                 SEARCH_SELECTOR_PKG);
+        tr.addOverride(R.array.config_notificationDefaultUnsupportedAdjustments,
+                new String[] {KEY_TYPE});
 
         doAnswer(invocation -> {
             mOnPermissionChangeListener = invocation.getArgument(2);
@@ -7662,7 +7664,7 @@
         when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
         when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
         when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
-        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
 
         // Set up notifications that will be adjusted
         final NotificationRecord r1 = spy(generateNotificationRecord(
@@ -17512,7 +17514,7 @@
                 NotificationManagerService.WorkerHandler.class);
         mService.setHandler(handler);
         when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
-        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
 
         Bundle signals = new Bundle();
         signals.putInt(KEY_TYPE, TYPE_NEWS);
@@ -17556,11 +17558,7 @@
                 NotificationManagerService.WorkerHandler.class);
         mService.setHandler(handler);
         when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
-        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), eq(TYPE_NEWS)))
-                .thenReturn(true);
-        // Blocking adjustments for a different type does nothing
-        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), eq(TYPE_PROMOTION)))
-                .thenReturn(false);
+        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
 
         Bundle signals = new Bundle();
         signals.putInt(KEY_TYPE, TYPE_NEWS);
@@ -17575,9 +17573,8 @@
 
         assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
 
-        // When we block adjustments for this package/type
-        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), eq(TYPE_PROMOTION)))
-                .thenReturn(false);
+        // When we block adjustments for this package
+        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(false);
 
         signals.putInt(KEY_TYPE, TYPE_PROMOTION);
         mBinderService.applyAdjustmentFromAssistant(null, adjustment);
@@ -17907,7 +17904,7 @@
         when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
         when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
         when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
-        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
 
         // Post a single notification
         final boolean hasOriginalSummary = false;
@@ -17947,7 +17944,7 @@
         when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
         when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
         when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
-        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
 
         // Post grouped notifications
         final String originalGroupName = "originalGroup";
@@ -17996,7 +17993,7 @@
         when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
         when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
         when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
-        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
 
         // Post grouped notifications
         final String originalGroupName = "originalGroup";
@@ -18047,7 +18044,7 @@
         when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
         when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
         when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
-        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true);
+        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
 
         // Post a single notification
         final boolean hasOriginalSummary = false;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6ef078b..31b9cf72 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -17,8 +17,10 @@
 package com.android.server.notification;
 
 import static android.app.AutomaticZenRule.TYPE_BEDTIME;
+import static android.app.AutomaticZenRule.TYPE_DRIVING;
 import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
 import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
+import static android.app.AutomaticZenRule.TYPE_THEATER;
 import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
 import static android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING;
 import static android.app.Flags.FLAG_MODES_API;
@@ -87,6 +89,7 @@
 import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
 import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
 import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
+import static com.android.server.notification.Flags.FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING;
 import static com.android.server.notification.ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL;
 import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
 
@@ -211,7 +214,9 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.io.Reader;
+import java.io.StringWriter;
 import java.time.Instant;
 import java.time.ZoneOffset;
 import java.time.temporal.ChronoUnit;
@@ -5516,8 +5521,72 @@
                 eq(ORIGIN_INIT_USER));
     }
 
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+    public void testDeviceEffects_allowsGrayscale() {
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        reset(mDeviceEffectsApplier);
+        ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+                .setShouldDisplayGrayscale(true)
+                .build();
+        String ruleId = addRuleWithEffects(TYPE_THEATER, grayscale);
+
+        mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+                ORIGIN_APP, CUSTOM_PKG_UID);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+    public void testDeviceEffects_whileDriving_avoidsGrayscale() {
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        reset(mDeviceEffectsApplier);
+        ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+                .setShouldDisplayGrayscale(true)
+                .build();
+        String ruleWithGrayscale = addRuleWithEffects(TYPE_THEATER, grayscale);
+        String drivingRule = addRuleWithEffects(TYPE_DRIVING, null);
+
+        mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleWithGrayscale,
+                CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+
+        mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, drivingRule, CONDITION_TRUE,
+                ORIGIN_APP, CUSTOM_PKG_UID);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), anyInt());
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+    public void testDeviceEffects_whileDrivingWithGrayscale_allowsGrayscale() {
+        mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+        reset(mDeviceEffectsApplier);
+        ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+                .setShouldDisplayGrayscale(true)
+                .build();
+        String weirdoDrivingWithGrayscale = addRuleWithEffects(TYPE_DRIVING, grayscale);
+
+        mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, weirdoDrivingWithGrayscale,
+                CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
+        mTestableLooper.processAllMessages();
+
+        verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+    }
+
     private String addRuleWithEffects(ZenDeviceEffects effects) {
+        return addRuleWithEffects(TYPE_UNKNOWN, effects);
+    }
+
+    private String addRuleWithEffects(int type, @Nullable ZenDeviceEffects effects) {
         AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setPackage(mContext.getPackageName())
+                .setType(type)
                 .setDeviceEffects(effects)
                 .build();
         return mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(),
@@ -7406,6 +7475,43 @@
                 .isEqualTo(mZenModeHelper.getDefaultZenPolicy());
     }
 
+    @Test
+    public void setAutomaticZenRuleState_logsOriginToZenLog() {
+        AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
+                .setPackage(mPkg)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, azr,
+                ORIGIN_APP, "adding", CUSTOM_PKG_UID);
+        ZenLog.clear();
+
+        // User enables manually from QS:
+        mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
+                new Condition(azr.getConditionId(), "", STATE_TRUE), ORIGIN_USER_IN_SYSTEMUI,
+                123456);
+
+        assertThat(getZenLog()).contains(
+                "config: setAzrState: " + ruleId + " (ORIGIN_USER_IN_SYSTEMUI) from uid " + 1234);
+    }
+
+    @Test
+    public void setAutomaticZenRuleStateFromConditionProvider_logsOriginToZenLog() {
+        AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond/cond"))
+                .setOwner(new ComponentName(CUSTOM_PKG_NAME, "SomeConditionProvider"))
+                .setPackage(mPkg)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, azr,
+                ORIGIN_APP, "adding", CUSTOM_PKG_UID);
+        ZenLog.clear();
+
+        // App enables rule through CPS
+        mZenModeHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT,
+                Uri.parse("cond/cond"), new Condition(azr.getConditionId(), "", STATE_TRUE),
+                ORIGIN_APP, CUSTOM_PKG_UID);
+
+        assertThat(getZenLog()).contains(
+                "config: setAzrStateFromCps: cond/cond (ORIGIN_APP) from uid " + CUSTOM_PKG_UID);
+    }
+
     private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
             @Nullable ZenPolicy zenPolicy) {
         ZenRule rule = new ZenRule();
@@ -7530,6 +7636,12 @@
         assertThat(dndProto.getNotificationList().getNumber()).isEqualTo(STATE_ALLOW);
     }
 
+    private static String getZenLog() {
+        StringWriter zenLogWriter = new StringWriter();
+        ZenLog.dump(new PrintWriter(zenLogWriter), "");
+        return zenLogWriter.toString();
+    }
+
     private static void withoutWtfCrash(Runnable test) {
         Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> {
         });
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index 08ae637..b328fc2 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -101,7 +101,6 @@
         "testng",
         "truth",
         "wmtests-support",
-        "display_flags_lib",
     ],
 
     libs: [
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 c88d515..65150e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2006,7 +2006,6 @@
         display.continueUpdateOrientationForDiffOrienLaunchingApp();
         assertTrue(display.isFixedRotationLaunchingApp(activity));
 
-        activity.stopFreezingScreen(true /* unfreezeSurfaceNow */, true /* force */);
         // Simulate the rotation has been updated to previous one, e.g. sensor updates before the
         // remote rotation is completed.
         doReturn(originalRotation).when(displayRotation).rotationForOrientation(
@@ -2015,14 +2014,10 @@
 
         final DisplayInfo rotatedInfo = activity.getFixedRotationTransformDisplayInfo();
         activity.finishFixedRotationTransform();
-        final ScreenRotationAnimation rotationAnim = display.getRotationAnimation();
-        assertNotNull(rotationAnim);
 
         // Because the display doesn't rotate, the rotated activity needs to cancel the fixed
         // rotation. There should be a rotation animation to cover the change of activity.
         verify(activity).onCancelFixedRotationTransform(rotatedInfo.rotation);
-        assertTrue(activity.isFreezingScreen());
-        assertFalse(displayRotation.isRotatingSeamlessly());
 
         // Simulate the remote rotation has completed and the configuration doesn't change, then
         // the rotated activity should also be restored by clearing the transform.
@@ -2041,8 +2036,6 @@
         activity.setVisibleRequested(false);
         clearInvocations(activity);
         activity.onCancelFixedRotationTransform(originalRotation);
-        // The implementation of cancellation must be executed.
-        verify(activity).startFreezingScreen(originalRotation);
     }
 
     @Test
@@ -2599,19 +2592,16 @@
         final TestWindowState appWindow = createWindowState(attrs, activity);
         activity.addWindow(appWindow);
         spyOn(appWindow);
-        doNothing().when(appWindow).onStartFreezingScreen();
 
         // Set initial orientation and update.
         activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-        mDisplayContent.updateOrientation(null /* freezeThisOneIfNeeded */,
-                false /* forceUpdate */);
+        mDisplayContent.updateOrientationAndComputeConfig(false /* forceUpdate */);
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation());
         appWindow.mResizeReported = false;
 
         // Update the orientation to perform 180 degree rotation and check that resize was reported.
         activity.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
-        mDisplayContent.updateOrientation(null /* freezeThisOneIfNeeded */,
-                false /* forceUpdate */);
+        mDisplayContent.updateOrientationAndComputeConfig(false /* forceUpdate */);
         // In this test, DC will not get config update. Set the waiting flag to false.
         mDisplayContent.mWaitingForConfig = false;
         mWm.mRoot.performSurfacePlacement();
@@ -2635,8 +2625,6 @@
         final TestWindowState appWindow = createWindowState(attrs, activity);
         activity.addWindow(appWindow);
         spyOn(appWindow);
-        doNothing().when(appWindow).onStartFreezingScreen();
-        doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
 
         // Set initial orientation and update.
         performRotation(displayRotation, Surface.ROTATION_90);
@@ -2795,9 +2783,6 @@
         assertEquals(Configuration.ORIENTATION_PORTRAIT, displayConfig.orientation);
         assertEquals(Configuration.ORIENTATION_PORTRAIT, activityConfig.orientation);
 
-        // Unblock the rotation animation, so the further orientation updates won't be ignored.
-        unblockDisplayRotation(activity.mDisplayContent);
-
         final ActivityRecord topActivity = createActivityRecord(activity.getTask());
         topActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
index 63dafcd..c667d76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -103,7 +103,7 @@
     @Test
     public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception {
         configureActivityAndDisplay();
-        when(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+        when(mActivity.mAppCompatController.getCameraOverrides()
                 .shouldRefreshActivityForCameraCompat()).thenReturn(false);
         mActivityRefresher.addEvaluator(mEvaluatorTrue);
 
@@ -161,7 +161,7 @@
         configureActivityAndDisplay();
         mActivityRefresher.addEvaluator(mEvaluatorTrue);
         doReturn(true)
-                .when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+                .when(mActivity.mAppCompatController.getCameraOverrides())
                     .shouldRefreshActivityViaPauseForCameraCompat();
 
         mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
@@ -174,7 +174,7 @@
         configureActivityAndDisplay();
         mActivityRefresher.addEvaluator(mEvaluatorTrue);
         doReturn(true)
-                .when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+                .when(mActivity.mAppCompatController.getCameraOverrides())
                     .shouldRefreshActivityViaPauseForCameraCompat();
 
         mActivityRefresher.onActivityRefreshed(mActivity);
@@ -188,7 +188,7 @@
 
     private void assertActivityRefreshRequested(boolean refreshRequested,
             boolean cycleThroughStop) throws Exception {
-        verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+        verify(mActivity.mAppCompatController.getCameraOverrides(),
                 times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
 
         final RefreshCallbackItem refreshCallbackItem =
@@ -212,9 +212,9 @@
                 .build()
                 .getTopMostActivity();
 
-        spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
+        spyOn(mActivity.mAppCompatController.getCameraOverrides());
         doReturn(true).when(mActivity).inFreeformWindowingMode();
         doReturn(true).when(mActivity.mAppCompatController
-                .getAppCompatCameraOverrides()).shouldRefreshActivityForCameraCompat();
+                .getCameraOverrides()).shouldRefreshActivityForCameraCompat();
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index 1d138e4..4ad1cd1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -393,7 +393,7 @@
         }
 
         private AppCompatCameraOverrides getAppCompatCameraOverrides() {
-            return activity().top().mAppCompatController.getAppCompatCameraOverrides();
+            return activity().top().mAppCompatController.getCameraOverrides();
         }
     }
 }
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 576d17a..716f864 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -401,7 +401,7 @@
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+        doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
                 .shouldRefreshActivityForCameraCompat();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -431,7 +431,7 @@
     public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+        doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
                 .shouldRefreshActivityViaPauseForCameraCompat();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -477,7 +477,7 @@
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
         final float configAspectRatio = 1.5f;
         mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
-        doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+        doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
                 .isOverrideMinAspectRatioForCameraEnabled();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -608,7 +608,7 @@
                 .build();
         mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
 
-        spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
+        spyOn(mActivity.mAppCompatController.getCameraOverrides());
         spyOn(mActivity.info);
 
         doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
@@ -630,7 +630,7 @@
 
     private void assertActivityRefreshRequested(boolean refreshRequested,
             boolean cycleThroughStop) throws Exception {
-        verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+        verify(mActivity.mAppCompatController.getCameraOverrides(),
                 times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
 
         final RefreshCallbackItem refreshCallbackItem =
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d76a907..0964ebe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1714,7 +1714,6 @@
 
     @Test
     public void testFinishFixedRotationNoAppTransitioningTask() {
-        unblockDisplayRotation(mDisplayContent);
         final ActivityRecord app = createActivityRecord(mDisplayContent);
         final Task task = app.getTask();
         final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
@@ -1742,7 +1741,6 @@
     public void testFixedRotationWithPip() {
         final DisplayContent displayContent = mDefaultDisplay;
         displayContent.setIgnoreOrientationRequest(false);
-        unblockDisplayRotation(displayContent);
         // Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded.
         doNothing().when(displayContent).prepareAppTransition(anyInt());
         // Make resume-top really update the activity state.
@@ -1762,7 +1760,6 @@
 
         assertTrue(displayContent.hasTopFixedRotationLaunchingApp());
         assertTrue(displayContent.mPinnedTaskController.shouldDeferOrientationChange());
-        verify(mWm, never()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
         clearInvocations(pinnedTask);
 
         // Assume that the PiP enter animation is done then the new bounds are set. Expect the
@@ -1799,7 +1796,6 @@
 
     @Test
     public void testNoFixedRotationOnResumedScheduledApp() {
-        unblockDisplayRotation(mDisplayContent);
         final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
         app.setVisible(false);
         app.setState(ActivityRecord.State.RESUMED, "test");
@@ -1811,7 +1807,7 @@
         // The condition should reject using fixed rotation because the resumed client in real case
         // might get display info immediately. And the fixed rotation adjustments haven't arrived
         // client side so the info may be inconsistent with the requested orientation.
-        verify(mDisplayContent).updateOrientation(eq(app), anyBoolean());
+        verify(mDisplayContent).updateOrientationAndComputeConfig(anyBoolean());
         assertFalse(app.isFixedRotationTransforming());
         assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
     }
@@ -1863,9 +1859,6 @@
 
     @Test
     public void testSecondaryInternalDisplayRotationFollowsDefaultDisplay() {
-        // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
-        doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
         final DisplayRotationCoordinator coordinator =
                 mRootWindowContainer.getDisplayRotationCoordinator();
         final DisplayContent defaultDisplayContent = mDisplayContent;
@@ -1921,9 +1914,6 @@
 
     @Test
     public void testSecondaryNonInternalDisplayDoesNotFollowDefaultDisplay() {
-        // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
-        doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
         final DisplayRotationCoordinator coordinator =
                 mRootWindowContainer.getDisplayRotationCoordinator();
 
@@ -1994,20 +1984,11 @@
                     }
                 };
 
-        // kill any existing rotation animation (vestigial from test setup).
-        dc.setRotationAnimation(null);
-
         mWm.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */);
-        // If remote rotation is not finished, the display should not be able to unfreeze.
-        mWm.stopFreezingDisplayLocked();
-        assertTrue(mWm.mDisplayFrozen);
 
         assertTrue(called[0]);
         waitUntilHandlersIdle();
         assertTrue(continued[0]);
-
-        mWm.stopFreezingDisplayLocked();
-        assertFalse(mWm.mDisplayFrozen);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 23c767c..02b796f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -267,7 +267,7 @@
     public void testTreatmentDisabledPerApp_noForceRotationOrRefresh()
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+        doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
                 .shouldForceRotateForCameraCompat();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -469,7 +469,7 @@
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
 
-        doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+        doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
                 .shouldRefreshActivityForCameraCompat();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -496,7 +496,7 @@
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
         doReturn(false).when(mActivity
-                        .mAppCompatController.getAppCompatCameraOverrides())
+                        .mAppCompatController.getCameraOverrides())
                             .isCameraCompatSplitScreenAspectRatioAllowed();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -509,7 +509,7 @@
     public void testOnActivityConfigurationChanging_splitScreenAspectRatioAllowed_refresh()
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+        doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
                 .isCameraCompatSplitScreenAspectRatioAllowed();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -535,7 +535,7 @@
     public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
             throws Exception {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+        doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
                 .shouldRefreshActivityViaPauseForCameraCompat();
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -599,7 +599,7 @@
                 ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
 
         spyOn(mActivity.mAtmService.getLifecycleManager());
-        spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
+        spyOn(mActivity.mAppCompatController.getCameraOverrides());
 
         doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
         doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
@@ -611,7 +611,7 @@
 
     private void assertActivityRefreshRequested(boolean refreshRequested,
                 boolean cycleThroughStop) throws Exception {
-        verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+        verify(mActivity.mAppCompatController.getCameraOverrides(),
                 times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
 
         final RefreshCallbackItem refreshCallbackItem =
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 6ced269..523b723 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -32,7 +32,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-import static com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_TOPOLOGY;
 import static com.android.server.wm.DragDropController.MSG_UNHANDLED_DROP_LISTENER_TIMEOUT;
 
 import static org.junit.Assert.assertEquals;
@@ -58,7 +57,6 @@
 import android.content.pm.ShortcutServiceInternal;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.hardware.display.VirtualDisplay;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -69,7 +67,6 @@
 import android.os.UserHandle;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.view.DragEvent;
 import android.view.InputChannel;
 import android.view.SurfaceControl;
@@ -83,14 +80,12 @@
 
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
-import com.android.server.wm.utils.VirtualDisplayTestRule;
 import com.android.window.flags.Flags;
 
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -111,8 +106,6 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class DragDropControllerTests extends WindowTestsBase {
-    @Rule
-    public VirtualDisplayTestRule mVirtualDisplayTestRule = new VirtualDisplayTestRule();
     private static final int TIMEOUT_MS = 3000;
     private static final int TEST_UID = 12345;
     private static final int TEST_PROFILE_UID = 12345 * UserHandle.PER_USER_RANGE;
@@ -228,17 +221,13 @@
 
     @After
     public void tearDown() throws Exception {
-        // Besides TestDragDropController, WMService also creates another DragDropController in
-        // test, and since listeners are added on instantiation, it has to be cleared here as well.
-        mTarget.cleanupListeners();
-        mWm.mDragDropController.cleanupListeners();
+        final CountDownLatch latch;
         if (!mTarget.dragDropActiveLocked()) {
             return;
         }
         if (mToken != null) {
             mTarget.cancelDragAndDrop(mToken, false);
         }
-        final CountDownLatch latch;
         latch = new CountDownLatch(1);
         mTarget.setOnClosedCallbackLocked(latch::countDown);
         if (mTarget.mIsAccessibilityDrag) {
@@ -577,10 +566,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(FLAG_DISPLAY_TOPOLOGY)
     @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND)
     public void testDragCancelledOnTopologyChange() {
-        VirtualDisplay virtualDisplay = createVirtualDisplay();
         // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
         // immediately after dispatching, which is a problem when using mockito arguments captor
         // because it returns and modifies the same drag event.
@@ -590,13 +577,8 @@
 
         startDrag(0, 0, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ,
                 ClipData.newPlainText("label", "text"), (surface) -> {
-                    final CountDownLatch latch = new CountDownLatch(1);
-                    mTarget.setOnClosedCallbackLocked(latch::countDown);
-
-                    // Release virtual display to trigger drag-and-drop cancellation.
-                    virtualDisplay.release();
-                    assertTrue(awaitInWmLock(() -> latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)));
-
+                    // Simulate display topology change to trigger drag-and-drop cancellation.
+                    mTarget.handleDisplayTopologyChange(null /* displayTopology */);
                     assertEquals(2, dragEvents.size());
                     assertEquals(ACTION_DRAG_ENDED, dragEvents.getLast().getAction());
                 });
@@ -979,12 +961,4 @@
         assertNotNull(mToken);
         r.run();
     }
-
-    private VirtualDisplay createVirtualDisplay() {
-        final int width = 800;
-        final int height = 600;
-        final String name = getClass().getSimpleName() + "_VirtualDisplay";
-        return mVirtualDisplayTestRule.createDisplayManagerAttachedVirtualDisplay(name, width,
-                height);
-    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index a84b374..2630565 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4683,7 +4683,8 @@
 
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
 
-        assertFalse(mActivity.isEligibleForLetterboxEducation());
+        assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+                .isEligibleForLetterboxEducation());
     }
 
     @Test
@@ -4694,7 +4695,8 @@
 
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
 
-        assertFalse(mActivity.isEligibleForLetterboxEducation());
+        assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+                .isEligibleForLetterboxEducation());
     }
 
     @Test
@@ -4716,7 +4718,8 @@
                 false /*moveParents*/, "test");
         organizer.mPrimary.setBounds(0, 0, 1000, 600);
 
-        assertFalse(mActivity.isEligibleForLetterboxEducation());
+        assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+                .isEligibleForLetterboxEducation());
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
     }
 
@@ -4728,7 +4731,8 @@
 
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
 
-        assertFalse(mActivity.isEligibleForLetterboxEducation());
+        assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+                .isEligibleForLetterboxEducation());
         assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
     }
@@ -4745,14 +4749,16 @@
                 createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING),
                         mActivity));
 
-        assertFalse(mActivity.isEligibleForLetterboxEducation());
+        assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+                .isEligibleForLetterboxEducation());
 
         // Verify that after removing the starting window isEligibleForLetterboxEducation returns
         // true and mTask.dispatchTaskInfoChangedIfNeeded is called.
         spyOn(mTask);
         mActivity.removeStartingWindow();
 
-        assertTrue(mActivity.isEligibleForLetterboxEducation());
+        assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+                .isEligibleForLetterboxEducation());
         verify(mTask).dispatchTaskInfoChangedIfNeeded(true);
     }
 
@@ -4768,14 +4774,16 @@
                 createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING),
                         mActivity));
 
-        assertFalse(mActivity.isEligibleForLetterboxEducation());
+        assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+                .isEligibleForLetterboxEducation());
 
         // Verify that after removing the starting window isEligibleForLetterboxEducation still
         // returns false and mTask.dispatchTaskInfoChangedIfNeeded isn't called.
         spyOn(mTask);
         mActivity.removeStartingWindow();
 
-        assertFalse(mActivity.isEligibleForLetterboxEducation());
+        assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+                .isEligibleForLetterboxEducation());
         verify(mTask, never()).dispatchTaskInfoChangedIfNeeded(true);
     }
 
@@ -4787,7 +4795,8 @@
 
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
 
-        assertTrue(mActivity.isEligibleForLetterboxEducation());
+        assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+                .isEligibleForLetterboxEducation());
         assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
     }
@@ -4802,7 +4811,8 @@
 
         rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
 
-        assertTrue(mActivity.isEligibleForLetterboxEducation());
+        assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+                .isEligibleForLetterboxEducation());
         assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertTrue(mActivity.inSizeCompatMode());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index a95093d..59335d3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -264,6 +264,7 @@
         spyOn(dmg);
         doNothing().when(dmg).registerDisplayListener(
                 any(), any(Executor.class), anyLong(), anyString());
+        doNothing().when(dmg).registerTopologyListener(any(Executor.class), any(), anyString());
     }
 
     private void setUpLocalServices() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 38d3d2f..724d7e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -92,6 +92,7 @@
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.SurfaceControl;
+import android.view.WindowInsetsController;
 import android.window.TaskFragmentOrganizer;
 
 import androidx.test.filters.MediumTest;
@@ -2109,6 +2110,43 @@
         assertEquals(Color.RED, task.getTaskDescription().getBackgroundColor());
     }
 
+    @Test
+    public void testUpdateTopOpaqueSystemBarsAppearanceWhenActivityBecomesTransparent() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord activity = createActivityRecord(task);
+        final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription();
+        td.setSystemBarsAppearance(
+                WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+        activity.setTaskDescription(td);
+
+        assertEquals(WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
+                task.getTaskDescription().getTopOpaqueSystemBarsAppearance());
+
+        activity.setOccludesParent(false);
+
+        assertEquals(0, task.getTaskDescription().getTopOpaqueSystemBarsAppearance());
+    }
+
+    @Test
+    public void testUpdateTopOpaqueSystemBarsAppearanceWhenActivityBecomesOpaque() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord activity = createActivityRecord(task);
+        activity.setOccludesParent(false);
+
+        final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription();
+        td.setSystemBarsAppearance(
+                WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+        activity.setTaskDescription(td);
+
+        assertEquals(0, task.getTaskDescription().getTopOpaqueSystemBarsAppearance());
+
+        activity.setOccludesParent(true);
+
+        assertEquals(WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
+                task.getTaskDescription().getTopOpaqueSystemBarsAppearance());
+
+    }
+
     private Task getTestTask() {
         return new TaskBuilder(mSupervisor).setCreateActivity(true).build();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
index e9ece5d..369600c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
@@ -79,6 +79,34 @@
     }
 
     @Test
+    public void testRemoveRootTask() {
+        final Task rootTask = createTask(mDisplayContent);
+        final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+        final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+        final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        WindowContainerToken token = rootTask.getTaskInfo().token;
+        wct.removeTask(token);
+        applyTransaction(wct);
+
+        // There is still an activity to be destroyed, so the task is not removed immediately.
+        assertNotNull(task.getParent());
+        assertTrue(rootTask.hasChild());
+        assertTrue(task.hasChild());
+        assertTrue(activity.finishing);
+
+        activity.destroyed("testRemoveRootTask");
+        // Assert that the container was removed after the activity is destroyed.
+        assertNull(task.getParent());
+        assertEquals(0, task.getChildCount());
+        assertNull(activity.getParent());
+        assertNull(taskDisplayArea.getTask(task1 -> task1.mTaskId == rootTask.mTaskId));
+        verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(task);
+        verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask);
+    }
+
+    @Test
     public void testDesktopMode_tasksAreBroughtToFront() {
         final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
         TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index f6f473b..cff172f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -855,12 +855,6 @@
         startingApp.updateResizingWindowIfNeeded();
         assertTrue(mWm.mResizingWindows.contains(startingApp));
         assertTrue(startingApp.isDrawn());
-
-        // Even if the display is frozen, invisible requested window should not be affected.
-        mWm.startFreezingDisplay(0, 0, mDisplayContent);
-        startingApp.getWindowFrames().setInsetsChanged(true);
-        startingApp.updateResizingWindowIfNeeded();
-        assertTrue(startingApp.isDrawn());
     }
 
     @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 37d2a75..2c390c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1010,20 +1010,6 @@
         waitUntilWindowAnimatorIdle();
     }
 
-    /**
-     * Avoids rotating screen disturbed by some conditions. It is usually used for the default
-     * display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
-     *
-     * @see DisplayRotation#updateRotationUnchecked
-     */
-    void unblockDisplayRotation(DisplayContent dc) {
-        dc.mOpeningApps.clear();
-        mWm.mAppsFreezingScreen = 0;
-        mWm.stopFreezingDisplayLocked();
-        // The rotation animation won't actually play, it needs to be cleared manually.
-        dc.setRotationAnimation(null);
-    }
-
     static void resizeDisplay(DisplayContent displayContent, int width, int height) {
         displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity,
                 displayContent.mBaseDisplayPhysicalXDpi, displayContent.mBaseDisplayPhysicalYDpi);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index a52614d..531f516 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -52,6 +52,8 @@
  * Represents an ongoing phone call that the in-call app should present to the user.
  */
 public final class Call {
+    private static final String LOG_TAG = "TelecomCall";
+
     /**
      * The state of a {@code Call} when newly created.
      */
@@ -2912,6 +2914,11 @@
                     }
                 } catch (BadParcelableException e) {
                     return false;
+                } catch (ClassCastException e) {
+                    Log.e(LOG_TAG, e, "areBundlesEqual: failure comparing bundle key %s", key);
+                    // until we know what is causing this, we should rethrow -- this is still not
+                    // expected.
+                    throw e;
                 }
             }
         }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 24fb8c5..da41655 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -180,7 +180,7 @@
  * permission-protected. Your application cannot access the protected
  * information unless it has the appropriate permissions declared in
  * its manifest file. Where permissions apply, they are noted in the
- * the methods through which you access the protected information.
+ * methods through which you access the protected information.
  *
  * <p>TelephonyManager is intended for use on devices that implement
  * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices
@@ -633,11 +633,14 @@
     }
 
     /**
-     * Returns the multi SIM variant
-     * Returns DSDS for Dual SIM Dual Standby
-     * Returns DSDA for Dual SIM Dual Active
-     * Returns TSTS for Triple SIM Triple Standby
-     * Returns UNKNOWN for others
+     * Returns the multi SIM variant.
+     *
+     * <ul>
+     *   <li>Returns DSDS for Dual SIM Dual Standby.</li>
+     *   <li>Returns DSDA for Dual SIM Dual Active.</li>
+     *   <li>Returns TSTS for Triple SIM Triple Standby.</li>
+     *   <li>Returns UNKNOWN for others.</li>
+     * </ul>
      */
     /** {@hide} */
     @UnsupportedAppUsage
@@ -657,10 +660,14 @@
 
     /**
      * Returns the number of phones available.
-     * Returns 0 if none of voice, sms, data is not supported
-     * Returns 1 for Single standby mode (Single SIM functionality).
-     * Returns 2 for Dual standby mode (Dual SIM functionality).
-     * Returns 3 for Tri standby mode (Tri SIM functionality).
+     *
+     * <ul>
+     *   <li>Returns 0 if none of voice, sms, data is supported.</li>
+     *   <li>Returns 1 for Single standby mode (Single SIM functionality).</li>
+     *   <li>Returns 2 for Dual standby mode (Dual SIM functionality).</li>
+     *   <li>Returns 3 for Tri standby mode (Tri SIM functionality).</li>
+     * </ul>
+     *
      * @deprecated Use {@link #getActiveModemCount} instead.
      */
     @Deprecated
@@ -671,10 +678,12 @@
     /**
      * Returns the number of logical modems currently configured to be activated.
      *
-     * Returns 0 if none of voice, sms, data is not supported
-     * Returns 1 for Single standby mode (Single SIM functionality).
-     * Returns 2 for Dual standby mode (Dual SIM functionality).
-     * Returns 3 for Tri standby mode (Tri SIM functionality).
+     * <ul>
+     *   <li>Returns 0 if none of voice, sms, data is supported.</li>
+     *   <li>Returns 1 for Single standby mode (Single SIM functionality).</li>
+     *   <li>Returns 2 for Dual standby mode (Dual SIM functionality).</li>
+     *   <li>Returns 3 for Tri standby mode (Tri SIM functionality).</li>
+     * </ul>
      */
     public int getActiveModemCount() {
         int modemCount = 1;