Merge "Revert^2 "InputManagerService: fix requireNonNull failure messages"" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index a9c4a15..6dd7521 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -36,7 +36,6 @@
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.UidObserver;
-import android.app.compat.CompatChanges;
 import android.app.job.JobInfo;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
@@ -54,6 +53,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
@@ -74,6 +74,7 @@
 import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
 import com.android.server.PowerAllowlistInternal;
+import com.android.server.compat.PlatformCompat;
 import com.android.server.job.ConstantsProto;
 import com.android.server.job.Flags;
 import com.android.server.job.JobSchedulerService;
@@ -157,6 +158,15 @@
     @Overridable // The change can be overridden in user build.
     static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS = 374323858L;
 
+    /**
+     * When enabled this change id overrides the default quota parameters adjustment.
+     */
+    @VisibleForTesting
+    @ChangeId
+    @Disabled // Disabled by default
+    @Overridable // The change can be overridden in user build.
+    static final long OVERRIDE_QUOTA_ADJUST_DEFAULT_CONSTANTS = 378129159L;
+
     @VisibleForTesting
     static class ExecutionStats {
         /**
@@ -536,6 +546,8 @@
      */
     private final SparseSetArray<String> mSystemInstallers = new SparseSetArray<>();
 
+    private final PlatformCompat mPlatformCompat;
+
     /** An app has reached its quota. The message should contain a {@link UserPackage} object. */
     @VisibleForTesting
     static final int MSG_REACHED_TIME_QUOTA = 0;
@@ -587,6 +599,13 @@
         PowerAllowlistInternal pai = LocalServices.getService(PowerAllowlistInternal.class);
         pai.registerTempAllowlistChangeListener(new TempAllowlistTracker());
 
+        mPlatformCompat = (PlatformCompat)
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
+        if (Flags.adjustQuotaDefaultConstants()) {
+            mPlatformCompat.registerListener(OVERRIDE_QUOTA_ADJUST_DEFAULT_CONSTANTS,
+                    (packageName) -> handleQuotaDefaultConstantsCompatChange());
+        }
+
         try {
             ActivityManager.getService().registerUidObserver(new QcUidObserver(),
                     ActivityManager.UID_OBSERVER_PROCSTATE,
@@ -651,8 +670,9 @@
 
         final int uid = jobStatus.getSourceUid();
         if ((!Flags.enforceQuotaPolicyToTopStartedJobs()
-                || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
-                        uid)) && mTopAppCache.get(uid)) {
+                || mPlatformCompat.isChangeEnabledByUid(
+                        OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, uid))
+                && mTopAppCache.get(uid)) {
             if (DEBUG) {
                 Slog.d(TAG, jobStatus.toShortString() + " is top started job");
             }
@@ -690,8 +710,8 @@
             }
         }
         if (!Flags.enforceQuotaPolicyToTopStartedJobs()
-                || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
-                        jobStatus.getSourceUid())) {
+                || mPlatformCompat.isChangeEnabledByUid(
+                        OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, jobStatus.getSourceUid())) {
             mTopStartedJobs.remove(jobStatus);
         }
     }
@@ -805,8 +825,8 @@
     /** @return true if the job was started while the app was in the TOP state. */
     private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) {
         if (!Flags.enforceQuotaPolicyToTopStartedJobs()
-                || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
-                        jobStatus.getSourceUid())) {
+                || mPlatformCompat.isChangeEnabledByUid(
+                        OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, jobStatus.getSourceUid())) {
             return mTopStartedJobs.contains(jobStatus);
         }
 
@@ -1102,6 +1122,7 @@
             final int standbyBucket) {
         final long baseLimitMs = mAllowedTimePerPeriodMs[standbyBucket];
         if (Flags.adjustQuotaDefaultConstants()
+                && !isCompatOverridedForQuotaConstantAdjustment()
                 && Flags.additionalQuotaForSystemInstaller()
                 && standbyBucket == EXEMPTED_INDEX
                 && mSystemInstallers.contains(userId, pkgName)) {
@@ -1473,10 +1494,21 @@
         }
     }
 
+    void handleQuotaDefaultConstantsCompatChange() {
+        synchronized (mLock) {
+            final boolean isCompatEnabled = isCompatOverridedForQuotaConstantAdjustment();
+            mQcConstants.adjustDefaultBucketWindowSizes(isCompatEnabled);
+            mQcConstants.adjustDefaultEjLimits(isCompatEnabled);
+            mQcConstants.mShouldReevaluateConstraints = true;
+            onConstantsUpdatedLocked();
+        }
+    }
+
     void processQuotaConstantsAdjustment() {
-        if (Flags.adjustQuotaDefaultConstants()) {
-            mQcConstants.adjustDefaultBucketWindowSizes();
-            mQcConstants.adjustDefaultEjLimits();
+        if (Flags.adjustQuotaDefaultConstants()
+                && !isCompatOverridedForQuotaConstantAdjustment()) {
+            mQcConstants.adjustDefaultBucketWindowSizes(false);
+            mQcConstants.adjustDefaultEjLimits(false);
         }
     }
 
@@ -1505,6 +1537,11 @@
         }
     }
 
+    private boolean isCompatOverridedForQuotaConstantAdjustment() {
+        return mPlatformCompat.isChangeEnabledByPackageName(
+                OVERRIDE_QUOTA_ADJUST_DEFAULT_CONSTANTS, "android", UserHandle.USER_SYSTEM);
+    }
+
     private void incrementTimingSessionCountLocked(final int userId,
             @NonNull final String packageName) {
         final long now = sElapsedRealtimeClock.millis();
@@ -2689,7 +2726,8 @@
     @VisibleForTesting
     int getProcessStateQuotaFreeThreshold(int uid) {
         if (Flags.enforceQuotaPolicyToFgsJobs()
-                && !CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS, uid)) {
+                && !mPlatformCompat.isChangeEnabledByUid(
+                        OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS, uid)) {
             return ActivityManager.PROCESS_STATE_BOUND_TOP;
         }
 
@@ -3596,25 +3634,40 @@
          */
         public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS;
 
-        void adjustDefaultBucketWindowSizes() {
-            ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters()
-                    ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS :
-                    DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
-            ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters()
-                    ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS :
-                    DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
-            ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS = Flags.tuneQuotaWindowDefaultParameters()
-                    ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS :
-                    DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS;
+        void adjustDefaultBucketWindowSizes(boolean useLegacyQuotaConstants) {
+            if (useLegacyQuotaConstants) {
+                ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+                        DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
+                ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+                        DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+                ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS =
+                        DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS;
 
-            WINDOW_SIZE_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters()
-                    ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS :
-                    DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS;
-            WINDOW_SIZE_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters()
-                    ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS :
-                    DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS;
-            WINDOW_SIZE_WORKING_MS = DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS;
-            WINDOW_SIZE_FREQUENT_MS = DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS;
+                WINDOW_SIZE_EXEMPTED_MS = DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS;
+                WINDOW_SIZE_ACTIVE_MS = DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS;
+                WINDOW_SIZE_WORKING_MS = DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS;
+                WINDOW_SIZE_FREQUENT_MS = DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS;
+            } else {
+                ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters()
+                        ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS :
+                        DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
+                ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters()
+                        ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS :
+                        DEFAULT_LEGACY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+                ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS =
+                        Flags.tuneQuotaWindowDefaultParameters()
+                                ? DEFAULT_CURRENT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS :
+                                DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS;
+
+                WINDOW_SIZE_EXEMPTED_MS = Flags.tuneQuotaWindowDefaultParameters()
+                        ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS :
+                        DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS;
+                WINDOW_SIZE_ACTIVE_MS = Flags.tuneQuotaWindowDefaultParameters()
+                        ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS :
+                        DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS;
+                WINDOW_SIZE_WORKING_MS = DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS;
+                WINDOW_SIZE_FREQUENT_MS = DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS;
+            }
 
             mAllowedTimePerPeriodMs[EXEMPTED_INDEX] = Math.min(MAX_PERIOD_MS,
                     Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS));
@@ -3640,10 +3693,15 @@
                             ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS);
         }
 
-        void adjustDefaultEjLimits() {
-            EJ_LIMIT_WORKING_MS = DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS;
-            EJ_TOP_APP_TIME_CHUNK_SIZE_MS = DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
-            EJ_REWARD_INTERACTION_MS = DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS;
+        void adjustDefaultEjLimits(boolean useLegacyQuotaConstants) {
+            EJ_LIMIT_WORKING_MS = useLegacyQuotaConstants ? DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS
+                    : DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS;
+            EJ_TOP_APP_TIME_CHUNK_SIZE_MS = useLegacyQuotaConstants
+                    ? DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS :
+                    DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
+            EJ_REWARD_INTERACTION_MS = useLegacyQuotaConstants
+                    ? DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS
+                    : DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS;
 
             // The limit must be in the range [15 minutes, active limit].
             mEJLimitsMs[WORKING_INDEX] = Math.max(15 * MINUTE_IN_MILLIS,
@@ -3668,6 +3726,8 @@
 
         public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
                 @NonNull String key) {
+            final boolean isCompatEnabled = isCompatOverridedForQuotaConstantAdjustment();
+
             switch (key) {
                 case KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS:
                 case KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS:
@@ -3835,7 +3895,8 @@
                 case KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS:
                     // We don't need to re-evaluate execution stats or constraint status for this.
                     EJ_TOP_APP_TIME_CHUNK_SIZE_MS =
-                            properties.getLong(key, Flags.adjustQuotaDefaultConstants()
+                            properties.getLong(key,
+                                    Flags.adjustQuotaDefaultConstants() && !isCompatEnabled
                                     ? DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS :
                                     DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS);
                     // Limit chunking to be in the range [1 millisecond, 15 minutes] per event.
@@ -3873,7 +3934,8 @@
                 case KEY_EJ_REWARD_INTERACTION_MS:
                     // We don't need to re-evaluate execution stats or constraint status for this.
                     EJ_REWARD_INTERACTION_MS =
-                            properties.getLong(key, Flags.adjustQuotaDefaultConstants()
+                            properties.getLong(key,
+                                    Flags.adjustQuotaDefaultConstants() && !isCompatEnabled
                                     ? DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS :
                                     DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS);
                     // Limit interaction reward to be in the range [5 seconds, 15 minutes] per
@@ -3914,6 +3976,8 @@
             }
             mExecutionPeriodConstantsUpdated = true;
 
+            final boolean isCompatEnabled = isCompatOverridedForQuotaConstantAdjustment();
+
             // Query the values as an atomic set.
             final DeviceConfig.Properties properties = DeviceConfig.getProperties(
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
@@ -3958,27 +4022,27 @@
             MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS,
                     DEFAULT_MAX_EXECUTION_TIME_MS);
             WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS,
-                    (Flags.adjustQuotaDefaultConstants()
+                    (Flags.adjustQuotaDefaultConstants() && !isCompatEnabled
                             && Flags.tuneQuotaWindowDefaultParameters())
                             ? DEFAULT_LATEST_WINDOW_SIZE_EXEMPTED_MS :
-                            (Flags.adjustQuotaDefaultConstants()
+                            (Flags.adjustQuotaDefaultConstants() && !isCompatEnabled
                                     ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS :
                                     DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS));
             WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS,
-                    (Flags.adjustQuotaDefaultConstants()
+                    (Flags.adjustQuotaDefaultConstants() && !isCompatEnabled
                             && Flags.tuneQuotaWindowDefaultParameters())
                             ? DEFAULT_LATEST_WINDOW_SIZE_ACTIVE_MS :
-                            (Flags.adjustQuotaDefaultConstants()
+                            (Flags.adjustQuotaDefaultConstants() && !isCompatEnabled
                                     ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS :
                                     DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS));
             WINDOW_SIZE_WORKING_MS =
                     properties.getLong(KEY_WINDOW_SIZE_WORKING_MS,
-                            Flags.adjustQuotaDefaultConstants()
+                            Flags.adjustQuotaDefaultConstants() && !isCompatEnabled
                                     ? DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS :
                                     DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS);
             WINDOW_SIZE_FREQUENT_MS =
                     properties.getLong(KEY_WINDOW_SIZE_FREQUENT_MS,
-                            Flags.adjustQuotaDefaultConstants()
+                            Flags.adjustQuotaDefaultConstants() && !isCompatEnabled
                                     ? DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS :
                                     DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS);
             WINDOW_SIZE_RARE_MS = properties.getLong(KEY_WINDOW_SIZE_RARE_MS,
@@ -4149,6 +4213,8 @@
             }
             mEJLimitConstantsUpdated = true;
 
+            final boolean isCompatEnabled = isCompatOverridedForQuotaConstantAdjustment();
+
             // Query the values as an atomic set.
             final DeviceConfig.Properties properties = DeviceConfig.getProperties(
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
@@ -4163,7 +4229,7 @@
             EJ_LIMIT_ACTIVE_MS = properties.getLong(
                     KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS);
             EJ_LIMIT_WORKING_MS = properties.getLong(
-                    KEY_EJ_LIMIT_WORKING_MS, Flags.adjustQuotaDefaultConstants()
+                    KEY_EJ_LIMIT_WORKING_MS, Flags.adjustQuotaDefaultConstants() && !isCompatEnabled
                             ? DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS :
                             DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS);
             EJ_LIMIT_FREQUENT_MS = properties.getLong(
diff --git a/core/api/current.txt b/core/api/current.txt
index 4cd6d6f..bba21f4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -24820,7 +24820,7 @@
     method public android.view.Surface getSurface();
     method public boolean isPrivacySensitive();
     method public void pause() throws java.lang.IllegalStateException;
-    method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
+    method @RequiresPermission(value=android.Manifest.permission.RECORD_AUDIO, conditional=true) public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
     method public void registerAudioRecordingCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioRecordingCallback);
     method public void release();
     method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
@@ -24861,7 +24861,7 @@
     method public void setVideoProfile(@NonNull android.media.EncoderProfiles.VideoProfile);
     method public void setVideoSize(int, int) throws java.lang.IllegalStateException;
     method public void setVideoSource(int) throws java.lang.IllegalStateException;
-    method public void start() throws java.lang.IllegalStateException;
+    method @RequiresPermission(value=android.Manifest.permission.RECORD_AUDIO, conditional=true) public void start() throws java.lang.IllegalStateException;
     method public void stop() throws java.lang.IllegalStateException;
     method public void unregisterAudioRecordingCallback(@NonNull android.media.AudioManager.AudioRecordingCallback);
     field public static final int MEDIA_ERROR_SERVER_DIED = 100; // 0x64
@@ -34264,6 +34264,7 @@
     method public boolean hasFileDescriptors();
     method public boolean hasFileDescriptors(int, int);
     method public byte[] marshall();
+    method @FlaggedApi("android.os.parcel_marshall_bytebuffer") public void marshall(@NonNull java.nio.ByteBuffer);
     method @NonNull public static android.os.Parcel obtain();
     method @NonNull public static android.os.Parcel obtain(@NonNull android.os.IBinder);
     method @Deprecated @Nullable public Object[] readArray(@Nullable ClassLoader);
@@ -34333,6 +34334,7 @@
     method public void setDataSize(int);
     method public void setPropagateAllowBlocking();
     method public void unmarshall(@NonNull byte[], int, int);
+    method @FlaggedApi("android.os.parcel_marshall_bytebuffer") public void unmarshall(@NonNull java.nio.ByteBuffer);
     method public void writeArray(@Nullable Object[]);
     method public void writeBinderArray(@Nullable android.os.IBinder[]);
     method public void writeBinderList(@Nullable java.util.List<android.os.IBinder>);
@@ -55423,6 +55425,7 @@
     method public void dispatchOnDraw();
     method public void dispatchOnGlobalLayout();
     method public boolean dispatchOnPreDraw();
+    method @FlaggedApi("android.view.flags.enable_dispatch_on_scroll_changed") public void dispatchOnScrollChanged();
     method public boolean isAlive();
     method public void registerFrameCommitCallback(@NonNull Runnable);
     method @Deprecated public void removeGlobalOnLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 35720fd..12f302a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7354,7 +7354,15 @@
 
   public class AudioDeviceVolumeManager {
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public android.media.VolumeInfo getDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
+    method @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE", android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes);
     method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public void setDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
+    method @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int);
+    field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; // 0x3
+    field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; // 0x5
+    field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; // 0x4
+    field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2
+    field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1
+    field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0
   }
 
   public final class AudioFocusInfo implements android.os.Parcelable {
@@ -7399,7 +7407,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
     method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
     method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
-    method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE", android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes);
+    method @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE", android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public java.util.List<java.lang.Integer> getIndependentStreamTypes();
     method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleStreamVolume(int);
@@ -7445,7 +7453,7 @@
     method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setBluetoothVariableLatencyEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setDeviceAsNonDefaultForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
-    method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int);
+    method @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForCapturePreset(int, @NonNull android.media.AudioDeviceAttributes);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
@@ -7465,12 +7473,12 @@
     field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int DEVICE_CONNECTION_STATE_CONNECTED = 1; // 0x1
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int DEVICE_CONNECTION_STATE_DISCONNECTED = 0; // 0x0
-    field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; // 0x3
-    field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; // 0x5
-    field public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; // 0x4
-    field public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2
-    field public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1
-    field public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0
+    field @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; // 0x3
+    field @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; // 0x5
+    field @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; // 0x4
+    field @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2
+    field @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1
+    field @Deprecated @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0
     field public static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
     field public static final String EXTRA_VOLUME_STREAM_VALUE = "android.media.EXTRA_VOLUME_STREAM_VALUE";
     field public static final int FLAG_BLUETOOTH_ABS_VOLUME = 64; // 0x40
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 4c82839..daa1902 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1955,6 +1955,10 @@
     method public static void enforceValidAudioDeviceTypeOut(int);
   }
 
+  public class AudioDeviceVolumeManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public boolean isFullVolumeDevice();
+  }
+
   public final class AudioFocusRequest {
     method @Nullable public android.media.AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener();
   }
@@ -2010,7 +2014,6 @@
     method @NonNull public android.media.VolumePolicy getVolumePolicy();
     method public boolean hasRegisteredDynamicPolicy();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean isCsdEnabled();
-    method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public boolean isFullVolumeDevice();
     method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean isVolumeControlUsingVolumeGroups();
     method public void permissionUpdateBarrier();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 62816a2..9cc7b8f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1188,7 +1188,7 @@
 
     /** @hide Should this process state be considered jank perceptible? */
     public static final boolean isProcStateJankPerceptible(int procState) {
-        if (Flags.jankPerceptibleNarrow()) {
+        if (Flags.jankPerceptibleNarrow() && !Flags.jankPerceptibleNarrowHoldback()) {
             return procState == PROCESS_STATE_PERSISTENT_UI
                 || procState == PROCESS_STATE_TOP
                 || procState == PROCESS_STATE_IMPORTANT_FOREGROUND
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 96b5096..f44c230 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4014,7 +4014,7 @@
             return VM_PROCESS_STATE_JANK_PERCEPTIBLE;
         }
 
-        if (Flags.jankPerceptibleNarrow()) {
+        if (Flags.jankPerceptibleNarrow() && !Flags.jankPerceptibleNarrowHoldback()) {
             // Unlike other persistent processes, system server is often on
             // the critical path for application startup. Mark it explicitly
             // as jank perceptible regardless of processState.
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 00fa1c1..bdecbae 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -302,13 +302,23 @@
 
     @Override
     public Intent getLaunchIntentForPackage(String packageName) {
+        return getLaunchIntentForPackage(packageName, false);
+    }
+
+    @Override
+    @Nullable
+    public Intent getLaunchIntentForPackage(@NonNull String packageName,
+            boolean includeDirectBootUnaware) {
+        ResolveInfoFlags queryFlags = ResolveInfoFlags.of(
+                includeDirectBootUnaware ? MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE : 0);
+
         // First see if the package has an INFO activity; the existence of
         // such an activity is implied to be the desired front-door for the
         // overall package (such as if it has multiple launcher entries).
         Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
         intentToResolve.addCategory(Intent.CATEGORY_INFO);
         intentToResolve.setPackage(packageName);
-        List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0);
+        List<ResolveInfo> ris = queryIntentActivities(intentToResolve, queryFlags);
 
         // Otherwise, try to find a main launcher activity.
         if (ris == null || ris.size() <= 0) {
@@ -316,7 +326,7 @@
             intentToResolve.removeCategory(Intent.CATEGORY_INFO);
             intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
             intentToResolve.setPackage(packageName);
-            ris = queryIntentActivities(intentToResolve, 0);
+            ris = queryIntentActivities(intentToResolve, queryFlags);
         }
         if (ris == null || ris.size() <= 0) {
             return null;
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index 2e8031d..2559bd0 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -231,9 +231,9 @@
     public static final int START_COMPONENT_OTHER = 5;
 
     /**
-     * @see #getMonoticCreationTimeMs
+     * @see #getMonotonicCreationTimeMs
      */
-    private long mMonoticCreationTimeMs;
+    private long mMonotonicCreationTimeMs;
 
     /**
      * @see #getStartupState
@@ -545,8 +545,8 @@
      *
      * @hide
      */
-    public long getMonoticCreationTimeMs() {
-        return mMonoticCreationTimeMs;
+    public long getMonotonicCreationTimeMs() {
+        return mMonotonicCreationTimeMs;
     }
 
     /**
@@ -751,14 +751,14 @@
         dest.writeParcelable(mStartIntent, flags);
         dest.writeInt(mLaunchMode);
         dest.writeBoolean(mWasForceStopped);
-        dest.writeLong(mMonoticCreationTimeMs);
+        dest.writeLong(mMonotonicCreationTimeMs);
         dest.writeInt(mStartComponent);
     }
     // LINT.ThenChange(:read_parcel)
 
     /** @hide */
     public ApplicationStartInfo(long monotonicCreationTimeMs) {
-        mMonoticCreationTimeMs = monotonicCreationTimeMs;
+        mMonotonicCreationTimeMs = monotonicCreationTimeMs;
     }
 
     /** @hide */
@@ -776,7 +776,7 @@
         mStartIntent = other.mStartIntent;
         mLaunchMode = other.mLaunchMode;
         mWasForceStopped = other.mWasForceStopped;
-        mMonoticCreationTimeMs = other.mMonoticCreationTimeMs;
+        mMonotonicCreationTimeMs = other.mMonotonicCreationTimeMs;
         mStartComponent = other.mStartComponent;
     }
 
@@ -803,7 +803,7 @@
                 in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class);
         mLaunchMode = in.readInt();
         mWasForceStopped = in.readBoolean();
-        mMonoticCreationTimeMs = in.readLong();
+        mMonotonicCreationTimeMs = in.readLong();
         mStartComponent = in.readInt();
     }
     // LINT.ThenChange(:write_parcel)
@@ -887,7 +887,7 @@
         }
         proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
         proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped);
-        proto.write(ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS, mMonoticCreationTimeMs);
+        proto.write(ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS, mMonotonicCreationTimeMs);
         proto.write(ApplicationStartInfoProto.START_COMPONENT, mStartComponent);
         proto.end(token);
     }
@@ -980,7 +980,7 @@
                             ApplicationStartInfoProto.WAS_FORCE_STOPPED);
                     break;
                 case (int) ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS:
-                    mMonoticCreationTimeMs = proto.readLong(
+                    mMonotonicCreationTimeMs = proto.readLong(
                             ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS);
                     break;
                 case (int) ApplicationStartInfoProto.START_COMPONENT:
@@ -999,7 +999,7 @@
         sb.append(prefix)
                 .append("ApplicationStartInfo ").append(seqSuffix).append(':')
                 .append('\n')
-                .append(" monotonicCreationTimeMs=").append(mMonoticCreationTimeMs)
+                .append(" monotonicCreationTimeMs=").append(mMonotonicCreationTimeMs)
                 .append('\n')
                 .append(" pid=").append(mPid)
                 .append(" realUid=").append(mRealUid)
@@ -1094,7 +1094,7 @@
                 && TextUtils.equals(mProcessName, o.mProcessName)
                 && timestampsEquals(o)
                 && mWasForceStopped == o.mWasForceStopped
-                && mMonoticCreationTimeMs == o.mMonoticCreationTimeMs
+                && mMonotonicCreationTimeMs == o.mMonotonicCreationTimeMs
                 && mStartComponent == o.mStartComponent;
     }
 
@@ -1102,7 +1102,7 @@
     public int hashCode() {
         return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
                 mStartType, mLaunchMode, mPackageName, mProcessName, mStartupTimestampsNs,
-                mMonoticCreationTimeMs, mStartComponent);
+                mMonotonicCreationTimeMs, mStartComponent);
     }
 
     private boolean timestampsEquals(@NonNull ApplicationStartInfo other) {
diff --git a/core/java/android/app/AutomaticZenRule.aidl b/core/java/android/app/AutomaticZenRule.aidl
index feb21d6..92f7d52 100644
--- a/core/java/android/app/AutomaticZenRule.aidl
+++ b/core/java/android/app/AutomaticZenRule.aidl
@@ -16,4 +16,6 @@
 
 package android.app;
 
-parcelable AutomaticZenRule;
\ No newline at end of file
+parcelable AutomaticZenRule;
+
+parcelable AutomaticZenRule.AzrWithId;
\ No newline at end of file
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index fa977c9..1ce38ac 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -228,7 +228,7 @@
     public AutomaticZenRule(Parcel source) {
         enabled = source.readInt() == ENABLED;
         if (source.readInt() == ENABLED) {
-            name = getTrimmedString(source.readString());
+            name = getTrimmedString(source.readString8());
         }
         interruptionFilter = source.readInt();
         conditionId = getTrimmedUri(source.readParcelable(null, android.net.Uri.class));
@@ -238,11 +238,11 @@
                 source.readParcelable(null, android.content.ComponentName.class));
         creationTime = source.readLong();
         mZenPolicy = source.readParcelable(null, ZenPolicy.class);
-        mPkg = source.readString();
+        mPkg = source.readString8();
         mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
         mAllowManualInvocation = source.readBoolean();
         mIconResId = source.readInt();
-        mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
+        mTriggerDescription = getTrimmedString(source.readString8(), MAX_DESC_LENGTH);
         mType = source.readInt();
     }
 
@@ -514,7 +514,7 @@
         dest.writeInt(enabled ? ENABLED : DISABLED);
         if (name != null) {
             dest.writeInt(1);
-            dest.writeString(name);
+            dest.writeString8(name);
         } else {
             dest.writeInt(0);
         }
@@ -524,11 +524,11 @@
         dest.writeParcelable(configurationActivity, 0);
         dest.writeLong(creationTime);
         dest.writeParcelable(mZenPolicy, 0);
-        dest.writeString(mPkg);
+        dest.writeString8(mPkg);
         dest.writeParcelable(mDeviceEffects, 0);
         dest.writeBoolean(mAllowManualInvocation);
         dest.writeInt(mIconResId);
-        dest.writeString(mTriggerDescription);
+        dest.writeString8(mTriggerDescription);
         dest.writeInt(mType);
     }
 
@@ -843,4 +843,41 @@
             return rule;
         }
     }
+
+    /** @hide */
+    public static final class AzrWithId implements Parcelable {
+        public final String mId;
+        public final AutomaticZenRule mRule;
+
+        public AzrWithId(String id, AutomaticZenRule rule) {
+            mId = id;
+            mRule = rule;
+        }
+
+        public static final Creator<AzrWithId> CREATOR = new Creator<>() {
+            @Override
+            public AzrWithId createFromParcel(Parcel in) {
+                return new AzrWithId(
+                        in.readString8(),
+                        in.readParcelable(AutomaticZenRule.class.getClassLoader(),
+                                AutomaticZenRule.class));
+            }
+
+            @Override
+            public AzrWithId[] newArray(int size) {
+                return new AzrWithId[size];
+            }
+        };
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeString8(mId);
+            dest.writeParcelable(mRule, flags);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+    }
 }
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 6efc4ef..3003b79 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -49,11 +49,14 @@
 import android.annotation.SuppressLint;
 import android.app.compat.CompatChanges;
 import android.app.role.RoleManager;
+import android.companion.virtual.VirtualDevice;
+import android.companion.virtual.VirtualDeviceManager;
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.Overridable;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.PermissionChecker;
 import android.content.pm.PackageManager;
@@ -67,6 +70,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
 import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -1174,17 +1178,48 @@
         @PackageManager.PermissionResult
         public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
                 String packageName, boolean allowWhileInUse) {
-            return checkPermission(context, mName, callerUid, callerPid, packageName,
-                    allowWhileInUse);
+            int permissionResult = checkPermission(context, mName, callerUid, callerPid,
+                    packageName, allowWhileInUse, Context.DEVICE_ID_DEFAULT);
+
+            if (permissionResult == PERMISSION_GRANTED
+                    || !PermissionManager.DEVICE_AWARE_PERMISSIONS.contains(mName)) {
+                return permissionResult;
+            }
+
+            // For device aware permissions, check if the permission is granted on any other
+            // active virtual device
+            VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+            if (vdm == null) {
+                return permissionResult;
+            }
+
+            final List<VirtualDevice> virtualDevices = vdm.getVirtualDevices();
+            for (int i = 0, size = virtualDevices.size(); i < size; i++) {
+                final VirtualDevice virtualDevice = virtualDevices.get(i);
+                int resolvedDeviceId = PermissionManager.resolveDeviceIdForPermissionCheck(
+                        context, virtualDevice.getDeviceId(), mName);
+                // we already checked on the default device context
+                if (resolvedDeviceId == Context.DEVICE_ID_DEFAULT) {
+                    continue;
+                }
+                permissionResult = checkPermission(context, mName, callerUid, callerPid,
+                        packageName, allowWhileInUse, resolvedDeviceId);
+                if (permissionResult == PERMISSION_GRANTED) {
+                    break;
+                }
+            }
+
+            return permissionResult;
         }
 
         @SuppressLint("AndroidFrameworkRequiresPermission")
         @PackageManager.PermissionResult
         int checkPermission(@NonNull Context context, @NonNull String name, int callerUid,
-                int callerPid, String packageName, boolean allowWhileInUse) {
+                int callerPid, String packageName, boolean allowWhileInUse, int deviceId) {
+            final AttributionSource attributionSource = new AttributionSource(callerUid,
+                    packageName, null /*attributionTag*/, deviceId);
             @PermissionCheckerManager.PermissionResult final int result =
-                    PermissionChecker.checkPermissionForPreflight(context, name,
-                            callerPid, callerUid, packageName);
+                    PermissionChecker.checkPermissionForPreflight(context, name, attributionSource);
             if (result == PERMISSION_HARD_DENIED) {
                 // If the user didn't grant this permission at all.
                 return PERMISSION_DENIED;
@@ -1196,7 +1231,7 @@
                         ? PERMISSION_GRANTED : PERMISSION_DENIED;
             }
             final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-            final int mode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, callerUid, packageName);
+            final int mode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, attributionSource);
             switch (mode) {
                 case MODE_ALLOWED:
                     // The appop is just allowed, plain and simple.
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 00df724..1f0cd39 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -224,7 +224,7 @@
     void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted);
     ZenPolicy getDefaultZenPolicy();
     AutomaticZenRule getAutomaticZenRule(String id);
-    Map<String, AutomaticZenRule> getAutomaticZenRules();
+    ParceledListSlice getAutomaticZenRules();
     String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg, boolean fromUser);
     boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule, boolean fromUser);
     boolean removeAutomaticZenRule(String id, boolean fromUser);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 8af5b1b..19fecb9 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -101,14 +101,20 @@
      */
     public static final String REPORT_KEY_STREAMRESULT = "stream";
 
-    static final String TAG = "Instrumentation";
+    /**
+     * @hide
+     */
+    public static final String TAG = "Instrumentation";
 
     private static final long CONNECT_TIMEOUT_MILLIS = 60_000;
 
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
-    // If set, will print the stack trace for activity starts within the process
-    static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE &&
+    /**
+     * If set, will print the stack trace for activity starts within the process
+     * @hide
+     */
+    public static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE &&
             SystemProperties.getBoolean("persist.wm.debug.start_activity", false);
     static final boolean DEBUG_FINISH_ACTIVITY = Build.IS_DEBUGGABLE &&
             SystemProperties.getBoolean("persist.wm.debug.finish_activity", false);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 050ef23..69e3ef9 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1747,7 +1747,15 @@
     public Map<String, AutomaticZenRule> getAutomaticZenRules() {
         INotificationManager service = service();
         try {
-            return service.getAutomaticZenRules();
+            Map<String, AutomaticZenRule> result = new HashMap<>();
+            ParceledListSlice<AutomaticZenRule.AzrWithId> parceledRules =
+                    service.getAutomaticZenRules();
+            if (parceledRules != null) {
+                for (AutomaticZenRule.AzrWithId rule : parceledRules.getList()) {
+                    result.put(rule.mId, rule.mRule);
+                }
+            }
+            return result;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index afe915e..dd87d28 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -594,7 +594,7 @@
      * @see PictureInPictureParams.Builder#setSeamlessResizeEnabled(boolean)
      */
     public boolean isSeamlessResizeEnabled() {
-        return mSeamlessResizeEnabled == null ? true : mSeamlessResizeEnabled;
+        return mSeamlessResizeEnabled == null ? false : mSeamlessResizeEnabled;
     }
 
     /**
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 9574824..7f1870b 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -248,7 +248,9 @@
     /**
      * Command for {@link #sendWallpaperCommand}: reported by System UI when the device keyguard
      * starts going away.
-     * This command is triggered by {@link android.app.IActivityTaskManager#keyguardGoingAway(int)}.
+     * <p>
+     * This command is triggered by {@link android.app.IActivityTaskManager#keyguardGoingAway(int)}
+     * or by {@link android.app.IActivityTaskManager#setLockScreenShown(boolean, boolean)}.
      *
      * @hide
      */
@@ -256,6 +258,18 @@
             "android.wallpaper.keyguardgoingaway";
 
     /**
+     * Command for {@link #sendWallpaperCommand}: reported by System UI when the device keyguard
+     * starts going away.
+     *
+     * <p>This command is triggered by
+     * {@link android.app.IActivityTaskManager#setLockScreenShown(boolean, boolean)}.
+     *
+     * @hide
+     */
+    public static final String COMMAND_KEYGUARD_APPEARING =
+            "android.wallpaper.keyguardappearing";
+
+    /**
      * Command for {@link #sendWallpaperCommand}: reported by System UI when the device is going to
      * sleep. The x and y arguments are a location (possibly very roughly) corresponding to the
      * action that caused the device to go to sleep. For example, if the power button was pressed,
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index c6d0f61..8c99bd8 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -790,12 +790,6 @@
                 || mWindowingMode == WINDOWING_MODE_MULTI_WINDOW;
     }
 
-    /** Returns true if the task bounds should persist across power cycles.
-     * @hide */
-    public boolean persistTaskBounds() {
-        return mWindowingMode == WINDOWING_MODE_FREEFORM;
-    }
-
     /**
      * Returns true if the tasks associated with this window configuration are floating.
      * Floating tasks are laid out differently as they are allowed to extend past the display bounds
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 720e045..29c84ee 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -163,3 +163,30 @@
     description: "Narrow the scope of Jank Perceptible"
     bug: "304837972"
 }
+
+flag {
+    name: "jank_perceptible_narrow_holdback"
+    namespace: "system_performance"
+    description: "Holdback study for jank_perceptible_narrow"
+    bug: "304837972"
+}
+
+flag {
+     namespace: "system_performance"
+     name: "app_start_info_cleanup_old_records"
+     description: "Cleanup old records to reduce size of in memory store."
+     bug: "384539178"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
+flag {
+     namespace: "system_performance"
+     name: "app_start_info_keep_records_sorted"
+     description: "Ensure records are kept sorted to avoid extra work"
+     bug: "384539178"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
diff --git a/core/java/android/app/admin/StringSetIntersection.java b/core/java/android/app/admin/StringSetIntersection.java
new file mode 100644
index 0000000..5f2031e
--- /dev/null
+++ b/core/java/android/app/admin/StringSetIntersection.java
@@ -0,0 +1,80 @@
+/*
+ * 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.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Set;
+
+/**
+ * Class to identify a intersection resolution mechanism for {@code Set<String>} policies, it's
+ * used to resolve the enforced policy when being set by multiple admins (see {@link
+ * PolicyState#getResolutionMechanism()}).
+ *
+ * @hide
+ */
+public final class StringSetIntersection extends ResolutionMechanism<Set<String>> {
+
+    /**
+     * Intersection resolution for policies represented {@code Set<String>} which resolves as the
+     * intersection of all sets.
+     */
+    @NonNull
+    public static final StringSetIntersection STRING_SET_INTERSECTION = new StringSetIntersection();
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        return o != null && getClass() == o.getClass();
+    }
+
+    @Override
+    public int hashCode() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "StringSetIntersection {}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {}
+
+    @NonNull
+    public static final Parcelable.Creator<StringSetIntersection> CREATOR =
+            new Parcelable.Creator<StringSetIntersection>() {
+                @Override
+                public StringSetIntersection createFromParcel(Parcel source) {
+                    return new StringSetIntersection();
+                }
+
+                @Override
+                public StringSetIntersection[] newArray(int size) {
+                    return new StringSetIntersection[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 572bffe..b87ef70 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -412,3 +412,13 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "use_policy_intersection_for_permitted_input_methods"
+  namespace: "enterprise"
+  description: "When deciding on permitted input methods, use policy intersection instead of last recorded policy."
+  bug: "340914586"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/app/wallpaper.aconfig b/core/java/android/app/wallpaper.aconfig
index 7aba172..4a15a72 100644
--- a/core/java/android/app/wallpaper.aconfig
+++ b/core/java/android/app/wallpaper.aconfig
@@ -24,6 +24,16 @@
 }
 
 flag {
+  name: "notify_keyguard_events"
+  namespace: "systemui"
+  description: "Send keyguard showing/hiding/going-away events to wallpaper as wallpaper commands (guarded by permission)"
+  bug: "395897130"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "accurate_wallpaper_downsampling"
   namespace: "systemui"
   description: "Accurate downsampling of wallpaper bitmap for high resolution images"
diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java
index a13af7f..d7d6262 100644
--- a/core/java/android/app/wallpaper/WallpaperDescription.java
+++ b/core/java/android/app/wallpaper/WallpaperDescription.java
@@ -168,6 +168,12 @@
         return mSampleSize;
     }
 
+    @Override
+    public String toString() {
+        String component = (mComponent != null) ? mComponent.toString() : "{null}";
+        return  component + ":" + mId;
+    }
+
     ////// Comparison overrides
 
     @Override
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 49fd634..53966b8 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5964,7 +5964,39 @@
      *
      * @see #getLaunchIntentSenderForPackage(String)
      */
-    public abstract @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName);
+     public abstract @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName);
+
+    /**
+     * Returns a "good" intent to launch a front-door activity in a package.
+     * This is used, for example, to implement an "open" button when browsing
+     * through packages.  The current implementation looks first for a main
+     * activity in the category {@link Intent#CATEGORY_INFO}, and next for a
+     * main activity in the category {@link Intent#CATEGORY_LAUNCHER}. Returns
+     * <code>null</code> if neither are found.
+     *
+     * <p>Consider using {@link #getLaunchIntentSenderForPackage(String)} if
+     * the caller is not allowed to query for the <code>packageName</code>.
+     *
+     * @param packageName The name of the package to inspect.
+     * @param includeDirectBootUnaware When {@code true}, activities that are direct-boot-unaware
+     *    will be considered even if the device hasn't been unlocked (i.e. querying will be done
+     *    with {@code MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE}).
+     *
+     * @return A fully-qualified {@link Intent} that can be used to launch the
+     * main activity in the package. Returns <code>null</code> if the package
+     * does not contain such an activity, or if <em>packageName</em> is not
+     * recognized.
+     *
+     * @see #getLaunchIntentSenderForPackage(String)
+     *
+     * @hide
+     */
+    public @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName,
+            boolean includeDirectBootUnaware) {
+        throw new UnsupportedOperationException(
+                "getLaunchIntentForPackage(packageName, includeDirectBootUnaware) not implemented"
+                        + " in subclass");
+    }
 
     /**
      * Return a "good" intent to launch a front-door Leanback activity in a
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 53203eb..c5412a9 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -494,10 +494,14 @@
     // TODO(b/142482943): Make this logic more specific and customizable. (canHaveProfile(userType))
     /* @hide */
     public boolean canHaveProfile() {
-        if (isProfile() || isGuest() || isRestricted()) {
+        if (!isFull() || isProfile() || isGuest() || isRestricted() || isDemo()) {
             return false;
         }
-        return isMain();
+        // NOTE: profiles used to be restricted just to the system user (and later to the main
+        // user), but from the framework point of view there is no need for such restriction, hence
+        // it's lifted
+        // TODO(b/374832167): check value of config_supportProfilesOnNonMainUser
+        return isMain() || android.multiuser.Flags.profilesForAll();
     }
 
     // TODO(b/142482943): Get rid of this (after removing it from all tests) if feasible.
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 3411a48..3dbd5b2 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -646,3 +646,10 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+     name: "profiles_for_all"
+     namespace: "multiuser"
+     description: "Allows any regular user to have profiles"
+     bug: "374832167"
+}
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index a7fbce5..7dc6afb 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -188,24 +188,6 @@
     int BIOMETRIC_ERROR_CONTENT_VIEW_MORE_OPTIONS_BUTTON = 22;
 
     /**
-     * The error code returned after lock out error happens, the error dialog shows, and the users
-     * dismisses the dialog. This is a placeholder that is currently only used by the support
-     * library.
-     *
-     * @hide
-     */
-    int BIOMETRIC_ERROR_LOCKOUT_ERROR_DIALOG_DISMISSED = 23;
-
-    /**
-     * The error code returned after biometric hardware error happens, the error dialog shows, and
-     * the users dismisses the dialog.This is a placeholder that is currently only used by the
-     * support library.
-     *
-     * @hide
-     */
-    int BIOMETRIC_ERROR_BIOMETRIC_HARDWARE_ERROR_DIALOG_DISMISSED = 24;
-
-    /**
      * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
      * because the authentication attempt was unsuccessful.
      * @hide
@@ -237,8 +219,6 @@
             BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE,
             BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS,
             BIOMETRIC_ERROR_CONTENT_VIEW_MORE_OPTIONS_BUTTON,
-            BIOMETRIC_ERROR_LOCKOUT_ERROR_DIALOG_DISMISSED,
-            BIOMETRIC_ERROR_BIOMETRIC_HARDWARE_ERROR_DIALOG_DISMISSED,
             BIOMETRIC_PAUSED_REJECTED})
     @Retention(RetentionPolicy.SOURCE)
     @interface Errors {}
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index 4ed0fc0..c3c8c3d 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -600,8 +600,7 @@
 
     private void addDisplay(int displayId, float width, float height, boolean shouldLog) {
         if (findDisplay(displayId, mRoot) != null) {
-            throw new IllegalArgumentException(
-                    "DisplayTopology: attempting to add a display that already exists");
+            return;
         }
         if (mRoot == null) {
             mRoot = new TreeNode(displayId, width, height, POSITION_LEFT, /* offset= */ 0);
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 2e7bc6d..84d96bd 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3504,6 +3504,10 @@
                 mInlineSuggestionSessionController.notifyOnStartInputView();
                 onStartInputView(mInputEditorInfo, restarting);
                 startExtractingText(true);
+                // Back callback is typically registered in {@link #showWindow()}, but it's possible
+                // for {@link #doStartInput()} to be called without {@link #showWindow()} so we also
+                // register here.
+                registerDefaultOnBackInvokedCallback();
             } else if (mCandidatesVisibility == View.VISIBLE) {
                 if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
                 mCandidatesViewStarted = true;
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index ee62dea..6b1e918 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -149,6 +149,11 @@
     private static volatile boolean sStackTrackingEnabled = false;
 
     /**
+     * The extension binder object
+     */
+    private IBinder mExtension = null;
+
+    /**
      * Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to
      * {@link TransactionTracker}.
      *
@@ -1237,7 +1242,9 @@
 
     /** @hide */
     @Override
-    public final native @Nullable IBinder getExtension();
+    public final @Nullable IBinder getExtension() {
+        return mExtension;
+    }
 
     /**
      * Set the binder extension.
@@ -1245,7 +1252,12 @@
      *
      * @hide
      */
-    public final native void setExtension(@Nullable IBinder extension);
+    public final void setExtension(@Nullable IBinder extension) {
+        mExtension = extension;
+        setExtensionNative(extension);
+    }
+
+    private final native void setExtensionNative(@Nullable IBinder extension);
 
     /**
      * Default implementation rewinds the parcels and calls onTransact. On
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 6cb49b3..4a99285 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -20,6 +20,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -27,6 +28,7 @@
 import android.annotation.TestApi;
 import android.app.AppOpsManager;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Flags;
 import android.ravenwood.annotation.RavenwoodClassLoadHook;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.ravenwood.annotation.RavenwoodReplace;
@@ -837,9 +839,8 @@
      * @param buffer The ByteBuffer to write the data to.
      * @throws ReadOnlyBufferException if the buffer is read-only.
      * @throws BufferOverflowException if the buffer is too small.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_PARCEL_MARSHALL_BYTEBUFFER)
     public final void marshall(@NonNull ByteBuffer buffer) {
         if (buffer == null) {
             throw new NullPointerException();
@@ -875,9 +876,8 @@
      * Fills the raw bytes of this Parcel with data from the supplied buffer.
      *
      * @param buffer will read buffer.remaining() bytes from the buffer.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_PARCEL_MARSHALL_BYTEBUFFER)
     public final void unmarshall(@NonNull ByteBuffer buffer) {
         if (buffer == null) {
             throw new NullPointerException();
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 86acb2b..0150d17 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -354,6 +354,15 @@
 
 flag {
      namespace: "system_performance"
+     name: "parcel_marshall_bytebuffer"
+     is_exported: true
+     description: "Parcel marshal/unmarshall APIs that use ByteBuffer."
+     is_fixed_read_only: true
+     bug: "401362825"
+}
+
+flag {
+     namespace: "system_performance"
      name: "perfetto_sdk_tracing"
      description: "Tracing using Perfetto SDK."
      bug: "303199244"
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 4cbd5be..fce2df1 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -62,6 +62,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Build;
@@ -460,14 +461,21 @@
     }
 
     private static void readRulesFromParcel(ArrayMap<String, ZenRule> ruleMap, Parcel source) {
-        final int len = source.readInt();
+        int len = source.readInt();
         if (len > 0) {
             final String[] ids = new String[len];
-            final ZenRule[] rules = new ZenRule[len];
-            source.readStringArray(ids);
-            source.readTypedArray(rules, ZenRule.CREATOR);
+            source.readString8Array(ids);
+            ParceledListSlice<?> parceledRules = source.readParcelable(
+                    ZenRule.class.getClassLoader(), ParceledListSlice.class);
+            List<?> rules = parceledRules != null ? parceledRules.getList() : new ArrayList<>();
+            if (rules.size() != len) {
+                Slog.wtf(TAG, String.format(
+                        "Unexpected parceled rules count (%s != %s), throwing them out",
+                        rules.size(), len));
+                len = 0;
+            }
             for (int i = 0; i < len; i++) {
-                ruleMap.put(ids[i], rules[i]);
+                ruleMap.put(ids[i], (ZenRule) rules.get(i));
             }
         }
     }
@@ -485,8 +493,8 @@
         }
         dest.writeInt(user);
         dest.writeParcelable(manualRule, 0);
-        writeRulesToParcel(automaticRules, dest);
-        writeRulesToParcel(deletedRules, dest);
+        writeRulesToParcel(automaticRules, dest, flags);
+        writeRulesToParcel(deletedRules, dest, flags);
         if (!Flags.modesUi()) {
             dest.writeInt(allowAlarms ? 1 : 0);
             dest.writeInt(allowMedia ? 1 : 0);
@@ -501,18 +509,19 @@
         }
     }
 
-    private static void writeRulesToParcel(ArrayMap<String, ZenRule> ruleMap, Parcel dest) {
+    private static void writeRulesToParcel(ArrayMap<String, ZenRule> ruleMap, Parcel dest,
+            int flags) {
         if (!ruleMap.isEmpty()) {
             final int len = ruleMap.size();
             final String[] ids = new String[len];
-            final ZenRule[] rules = new ZenRule[len];
+            final ArrayList<ZenRule> rules = new ArrayList<>();
             for (int i = 0; i < len; i++) {
                 ids[i] = ruleMap.keyAt(i);
-                rules[i] = ruleMap.valueAt(i);
+                rules.add(ruleMap.valueAt(i));
             }
             dest.writeInt(len);
-            dest.writeStringArray(ids);
-            dest.writeTypedArray(rules, 0);
+            dest.writeString8Array(ids);
+            dest.writeParcelable(new ParceledListSlice<>(rules), flags);
         } else {
             dest.writeInt(0);
         }
@@ -2636,7 +2645,7 @@
             enabled = source.readInt() == 1;
             snoozing = source.readInt() == 1;
             if (source.readInt() == 1) {
-                name = source.readString();
+                name = source.readString8();
             }
             zenMode = source.readInt();
             conditionId = source.readParcelable(null, android.net.Uri.class);
@@ -2644,18 +2653,18 @@
             component = source.readParcelable(null, android.content.ComponentName.class);
             configurationActivity = source.readParcelable(null, android.content.ComponentName.class);
             if (source.readInt() == 1) {
-                id = source.readString();
+                id = source.readString8();
             }
             creationTime = source.readLong();
             if (source.readInt() == 1) {
-                enabler = source.readString();
+                enabler = source.readString8();
             }
             zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
             zenDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
-            pkg = source.readString();
+            pkg = source.readString8();
             allowManualInvocation = source.readBoolean();
-            iconResName = source.readString();
-            triggerDescription = source.readString();
+            iconResName = source.readString8();
+            triggerDescription = source.readString8();
             type = source.readInt();
             userModifiedFields = source.readInt();
             zenPolicyUserModifiedFields = source.readInt();
@@ -2703,7 +2712,7 @@
             dest.writeInt(snoozing ? 1 : 0);
             if (name != null) {
                 dest.writeInt(1);
-                dest.writeString(name);
+                dest.writeString8(name);
             } else {
                 dest.writeInt(0);
             }
@@ -2714,23 +2723,23 @@
             dest.writeParcelable(configurationActivity, 0);
             if (id != null) {
                 dest.writeInt(1);
-                dest.writeString(id);
+                dest.writeString8(id);
             } else {
                 dest.writeInt(0);
             }
             dest.writeLong(creationTime);
             if (enabler != null) {
                 dest.writeInt(1);
-                dest.writeString(enabler);
+                dest.writeString8(enabler);
             } else {
                 dest.writeInt(0);
             }
             dest.writeParcelable(zenPolicy, 0);
             dest.writeParcelable(zenDeviceEffects, 0);
-            dest.writeString(pkg);
+            dest.writeString8(pkg);
             dest.writeBoolean(allowManualInvocation);
-            dest.writeString(iconResName);
-            dest.writeString(triggerDescription);
+            dest.writeString8(iconResName);
+            dest.writeString8(triggerDescription);
             dest.writeInt(type);
             dest.writeInt(userModifiedFields);
             dest.writeInt(zenPolicyUserModifiedFields);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 6b7b818..7e9dfe6 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1030,10 +1030,18 @@
                 handlePendingControlRequest(statsToken);
             } else {
                 if (showTypes[0] != 0) {
+                    if ((showTypes[0] & ime()) != 0) {
+                        ImeTracker.forLogging().onProgress(statsToken,
+                                ImeTracker.PHASE_CLIENT_ON_CONTROLS_CHANGED);
+                    }
                     applyAnimation(showTypes[0], true /* show */, false /* fromIme */,
                             false /* skipsCallbacks */, statsToken);
                 }
                 if (hideTypes[0] != 0) {
+                    if ((hideTypes[0] & ime()) != 0) {
+                        ImeTracker.forLogging().onProgress(statsToken,
+                                ImeTracker.PHASE_CLIENT_ON_CONTROLS_CHANGED);
+                    }
                     applyAnimation(hideTypes[0], false /* show */, false /* fromIme */,
                             // The animation of hiding transient types shouldn't be detected by the
                             // app. Otherwise, it might be able to react to the callbacks and cause
@@ -1041,6 +1049,10 @@
                             (hideTypes[0] & ~transientTypes[0]) == 0 /* skipsCallbacks */,
                             statsToken);
                 }
+                if ((showTypes[0] & ime()) == 0 && (hideTypes[0] & ime()) == 0) {
+                    ImeTracker.forLogging().onCancelled(statsToken,
+                            ImeTracker.PHASE_CLIENT_ON_CONTROLS_CHANGED);
+                }
             }
         } else {
             if (showTypes[0] != 0) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1b57b00..94e9aa7 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1070,9 +1070,9 @@
             }
 
             if (mSurfacePackage != null) {
-                mSurfaceControlViewHostParent.detach();
                 mEmbeddedWindowParams.clear();
                 if (releaseSurfacePackage) {
+                    mSurfaceControlViewHostParent.detach();
                     mSurfacePackage.release();
                     mSurfacePackage = null;
                 }
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 3b444c4..fc66e49 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -16,6 +16,9 @@
 
 package android.view;
 
+import static android.view.flags.Flags.FLAG_ENABLE_DISPATCH_ON_SCROLL_CHANGED;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -1258,8 +1261,9 @@
     /**
      * Notifies registered listeners that something has scrolled.
      */
+    @FlaggedApi(FLAG_ENABLE_DISPATCH_ON_SCROLL_CHANGED)
     @UnsupportedAppUsage
-    final void dispatchOnScrollChanged() {
+    public final void dispatchOnScrollChanged() {
         // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
         // perform the dispatching. The iterator is a safe guard against listeners that
         // could mutate the list by calling the various add/remove methods. This prevents
diff --git a/core/java/android/view/flags/view_tree_observer_flags.aconfig b/core/java/android/view/flags/view_tree_observer_flags.aconfig
new file mode 100644
index 0000000..82f3300
--- /dev/null
+++ b/core/java/android/view/flags/view_tree_observer_flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.view.flags"
+container: "system"
+
+flag {
+    name: "enable_dispatch_on_scroll_changed"
+    namespace: "toolkit"
+    description: "Feature flag for enabling the dispatchOnScrollChanged method in ViewTreeObserver."
+    bug: "238109286"
+}
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 5dadf32..b1ba8b3 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -231,6 +231,7 @@
             PHASE_WM_WINDOW_ANIMATING_TYPES_CHANGED,
             PHASE_WM_NOTIFY_HIDE_ANIMATION_FINISHED,
             PHASE_WM_UPDATE_DISPLAY_WINDOW_ANIMATING_TYPES,
+            PHASE_CLIENT_ON_CONTROLS_CHANGED,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Phase {}
@@ -469,6 +470,9 @@
     /** The control target reported its animatingTypes back to WindowManagerService. */
     int PHASE_WM_UPDATE_DISPLAY_WINDOW_ANIMATING_TYPES =
             ImeProtoEnums.PHASE_WM_UPDATE_DISPLAY_WINDOW_ANIMATING_TYPES;
+    /** InsetsController received a control for the IME. */
+    int PHASE_CLIENT_ON_CONTROLS_CHANGED =
+            ImeProtoEnums.PHASE_CLIENT_ON_CONTROLS_CHANGED;
 
     /**
      * Called when an IME request is started.
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 2ed9c3a..8f7f941 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -16,6 +16,7 @@
 
 package android.window;
 
+import static android.app.Instrumentation.DEBUG_START_ACTIVITY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
@@ -32,6 +33,7 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
+import android.app.Instrumentation;
 import android.app.PendingIntent;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
@@ -45,6 +47,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
 import android.view.SurfaceControl;
@@ -642,6 +645,10 @@
      */
     @NonNull
     public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) {
+        if (DEBUG_START_ACTIVITY) {
+            Log.d(Instrumentation.TAG, "WCT.startTask: taskId=" + taskId
+                    + " options=" + options, new Throwable());
+        }
         mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options));
         return this;
     }
@@ -655,11 +662,15 @@
      */
     @NonNull
     public WindowContainerTransaction sendPendingIntent(@Nullable PendingIntent sender,
-            @Nullable Intent intent, @Nullable Bundle options) {
+            @Nullable Intent fillInIntent, @Nullable Bundle options) {
+        if (DEBUG_START_ACTIVITY) {
+            Log.d(Instrumentation.TAG, "WCT.sendPendingIntent: sender=" + sender.getIntent()
+                    + " fillInIntent=" + fillInIntent + " options=" + options, new Throwable());
+        }
         mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT)
                 .setLaunchOptions(options)
                 .setPendingIntent(sender)
-                .setActivityIntent(intent)
+                .setActivityIntent(fillInIntent)
                 .build());
         return this;
     }
@@ -674,6 +685,10 @@
     @NonNull
     public WindowContainerTransaction startShortcut(@NonNull String callingPackage,
             @NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) {
+        if (DEBUG_START_ACTIVITY) {
+            Log.d(Instrumentation.TAG, "WCT.startShortcut: shortcutInfo=" + shortcutInfo
+                    + " options=" + options, new Throwable());
+        }
         mHierarchyOps.add(HierarchyOp.createForStartShortcut(
                 callingPackage, shortcutInfo, options));
         return this;
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 8162702..8dd0457 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -287,6 +287,17 @@
 }
 
 flag {
+  name: "use_visible_requested_for_process_tracker"
+  namespace: "windowing_frontend"
+  description: "Do not count closing activity as visible process"
+  bug: "396653764"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "ensure_wallpaper_in_transitions"
   namespace: "windowing_frontend"
   description: "Ensure that wallpaper window tokens are always present/available for collection in transitions"
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index e125e25..c25f6b1 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -322,8 +322,18 @@
      */
     public static final int CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY = 129;
 
+    /**
+     * Track the animation of an ongoing call app back into its status bar chip (displaying the call
+     * icon and timer) when returning Home.
+     *
+     * <p>Tracking starts when the RemoteTransition registered to handle the transition from the app
+     * to Home is sent the onAnimationStart() signal and start the animation. Tracking ends when
+     * the animation is fully settled and the transition is complete.</p>
+     */
+    public static final int CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP = 130;
+
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
-    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY;
+    @VisibleForTesting static final int LAST_CUJ = CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP;
 
     /** @hide */
     @IntDef({
@@ -444,7 +454,8 @@
             CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND,
             CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK,
             CUJ_DEFAULT_TASK_TO_TASK_ANIMATION,
-            CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY
+            CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY,
+            CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {}
@@ -576,6 +587,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WORK_UTILITY_VIEW_SHRINK;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DEFAULT_TASK_TO_TASK_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DEFAULT_TASK_TO_TASK_ANIMATION;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_RETURN_TO_CALL_CHIP;
     }
 
     private Cuj() {
@@ -830,6 +842,8 @@
                 return "DEFAULT_TASK_TO_TASK_ANIMATION";
             case CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY:
                 return "DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY";
+            case CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP:
+                return "STATUS_BAR_APP_RETURN_TO_CALL_CHIP";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 8151429..f1c47a7 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -760,9 +760,20 @@
                     break;
                 }
 
-                if (fragment.monotonicTimeMs >= startTimeMs && fragment != mActiveFragment) {
-                    containers.add(new BatteryHistoryParcelContainer(fragment));
+                if (fragment.monotonicTimeMs >= mHistoryBufferStartTime) {
+                    // Do not include the backup of the current buffer, which is explicitly
+                    // included later
+                    continue;
                 }
+
+                if (i < fragments.size() - 1
+                        && fragments.get(i + 1).monotonicTimeMs < startTimeMs) {
+                    // Since fragments are ordered, an early start of next fragment implies an
+                    // early end for this one.
+                    continue;
+                }
+
+                containers.add(new BatteryHistoryParcelContainer(fragment));
             }
         }
 
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 3472d68..f6e2a4d 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -78,6 +78,13 @@
     @Nullable
     private List<DrawablePart> mProgressDrawableParts = null;
 
+    /** @see R.styleable#NotificationProgressBar_segMinWidth */
+    private final float mSegMinWidth;
+    /** @see R.styleable#NotificationProgressBar_segSegGap */
+    private final float mSegSegGap;
+    /** @see R.styleable#NotificationProgressBar_segPointGap */
+    private final float mSegPointGap;
+
     @Nullable
     private Drawable mTracker = null;
     private boolean mHasTrackerIcon = false;
@@ -128,6 +135,10 @@
             Log.e(TAG, "Can't get NotificationProgressDrawable", ex);
         }
 
+        mSegMinWidth = a.getDimension(R.styleable.NotificationProgressBar_segMinWidth, 0f);
+        mSegSegGap = a.getDimension(R.styleable.NotificationProgressBar_segSegGap, 0f);
+        mSegPointGap = a.getDimension(R.styleable.NotificationProgressBar_segPointGap, 0f);
+
         // Supports setting the tracker in xml, but ProgressStyle notifications set/override it
         // via {@code #setProgressTrackerIcon}.
         final Drawable tracker = a.getDrawable(R.styleable.NotificationProgressBar_tracker);
@@ -444,30 +455,26 @@
             return;
         }
 
-        final float segSegGap = mNotificationProgressDrawable.getSegSegGap();
-        final float segPointGap = mNotificationProgressDrawable.getSegPointGap();
         final float pointRadius = mNotificationProgressDrawable.getPointRadius();
         mProgressDrawableParts = processPartsAndConvertToDrawableParts(
                 mParts,
                 width,
-                segSegGap,
-                segPointGap,
+                mSegSegGap,
+                mSegPointGap,
                 pointRadius,
                 mHasTrackerIcon,
                 mTrackerDrawWidth
         );
 
-        final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth();
         final float progressFraction = getProgressFraction();
         final boolean isStyledByProgress = mProgressModel.isStyledByProgress();
-        final float progressGap =
-                mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap();
+        final float progressGap = mHasTrackerIcon ? 0F : mSegSegGap;
         Pair<List<DrawablePart>, Float> p = null;
         try {
             p = maybeStretchAndRescaleSegments(
                     mParts,
                     mProgressDrawableParts,
-                    segmentMinWidth,
+                    mSegMinWidth,
                     pointRadius,
                     progressFraction,
                     isStyledByProgress,
@@ -492,11 +499,11 @@
                         mProgressModel.getProgress(),
                         getMax(),
                         width,
-                        segSegGap,
-                        segPointGap,
+                        mSegSegGap,
+                        mSegPointGap,
                         pointRadius,
                         mHasTrackerIcon,
-                        segmentMinWidth,
+                        mSegMinWidth,
                         isStyledByProgress,
                         mTrackerDrawWidth);
             } catch (NotEnoughWidthToFitAllPartsException ex) {
@@ -521,11 +528,11 @@
                         mProgressModel.getProgress(),
                         getMax(),
                         width,
-                        segSegGap,
-                        segPointGap,
+                        mSegSegGap,
+                        mSegPointGap,
                         pointRadius,
                         mHasTrackerIcon,
-                        segmentMinWidth,
+                        mSegMinWidth,
                         isStyledByProgress,
                         mTrackerDrawWidth);
             } catch (NotEnoughWidthToFitAllPartsException ex) {
diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
index b109610..32b283a 100644
--- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java
+++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
@@ -84,27 +84,6 @@
     }
 
     /**
-     * Returns the gap between two segments.
-     */
-    public float getSegSegGap() {
-        return mState.mSegSegGap;
-    }
-
-    /**
-     * Returns the gap between a segment and a point.
-     */
-    public float getSegPointGap() {
-        return mState.mSegPointGap;
-    }
-
-    /**
-     * Returns the gap between a segment and a point.
-     */
-    public float getSegmentMinWidth() {
-        return mState.mSegmentMinWidth;
-    }
-
-    /**
      * Returns the radius for the points.
      */
     public float getPointRadius() {
@@ -241,11 +220,6 @@
 
         mState.setDensity(resolveDensity(r, 0));
 
-        final TypedArray a = obtainAttributes(r, theme, attrs,
-                R.styleable.NotificationProgressDrawable);
-        updateStateFromTypedArray(a);
-        a.recycle();
-
         inflateChildElements(r, parser, attrs, theme);
 
         updateLocalState();
@@ -262,13 +236,6 @@
 
         state.setDensity(resolveDensity(t.getResources(), 0));
 
-        if (state.mThemeAttrs != null) {
-            final TypedArray a = t.resolveAttributes(
-                    state.mThemeAttrs, R.styleable.NotificationProgressDrawable);
-            updateStateFromTypedArray(a);
-            a.recycle();
-        }
-
         applyThemeChildElements(t);
 
         updateLocalState();
@@ -279,21 +246,6 @@
         return (mState.canApplyTheme()) || super.canApplyTheme();
     }
 
-    private void updateStateFromTypedArray(TypedArray a) {
-        final State state = mState;
-
-        // Account for any configuration changes.
-        state.mChangingConfigurations |= a.getChangingConfigurations();
-
-        // Extract the theme attributes, if any.
-        state.mThemeAttrs = a.extractThemeAttrs();
-
-        state.mSegSegGap = a.getDimension(R.styleable.NotificationProgressDrawable_segSegGap,
-                state.mSegSegGap);
-        state.mSegPointGap = a.getDimension(R.styleable.NotificationProgressDrawable_segPointGap,
-                state.mSegPointGap);
-    }
-
     private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
             Theme theme) throws XmlPullParserException, IOException {
         TypedArray a;
@@ -357,8 +309,6 @@
         // Extract the theme attributes, if any.
         state.mThemeAttrsSegments = a.extractThemeAttrs();
 
-        state.mSegmentMinWidth = a.getDimension(
-                R.styleable.NotificationProgressDrawableSegments_minWidth, state.mSegmentMinWidth);
         state.mSegmentHeight = a.getDimension(
                 R.styleable.NotificationProgressDrawableSegments_height, state.mSegmentHeight);
         state.mFadedSegmentHeight = a.getDimension(
@@ -588,9 +538,6 @@
     static final class State extends ConstantState {
         @Config
         int mChangingConfigurations;
-        float mSegSegGap = 0.0f;
-        float mSegPointGap = 0.0f;
-        float mSegmentMinWidth = 0.0f;
         float mSegmentHeight;
         float mFadedSegmentHeight;
         float mSegmentCornerRadius;
@@ -610,9 +557,6 @@
 
         State(@NonNull State orig, @Nullable Resources res) {
             mChangingConfigurations = orig.mChangingConfigurations;
-            mSegSegGap = orig.mSegSegGap;
-            mSegPointGap = orig.mSegPointGap;
-            mSegmentMinWidth = orig.mSegmentMinWidth;
             mSegmentHeight = orig.mSegmentHeight;
             mFadedSegmentHeight = orig.mFadedSegmentHeight;
             mSegmentCornerRadius = orig.mSegmentCornerRadius;
@@ -631,18 +575,6 @@
         }
 
         private void applyDensityScaling(int sourceDensity, int targetDensity) {
-            if (mSegSegGap > 0) {
-                mSegSegGap = scaleFromDensity(
-                        mSegSegGap, sourceDensity, targetDensity);
-            }
-            if (mSegPointGap > 0) {
-                mSegPointGap = scaleFromDensity(
-                        mSegPointGap, sourceDensity, targetDensity);
-            }
-            if (mSegmentMinWidth > 0) {
-                mSegmentMinWidth = scaleFromDensity(
-                        mSegmentMinWidth, sourceDensity, targetDensity);
-            }
             if (mSegmentHeight > 0) {
                 mSegmentHeight = scaleFromDensity(
                         mSegmentHeight, sourceDensity, targetDensity);
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 766fbf1..8fbd10c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -64,16 +64,16 @@
     private static final boolean DEBUG = false;
 
     // Semantic version
-    public static final int MAJOR_VERSION = 0;
-    public static final int MINOR_VERSION = 4;
+    public static final int MAJOR_VERSION = 1;
+    public static final int MINOR_VERSION = 0;
     public static final int PATCH_VERSION = 0;
 
     // Internal version level
-    public static final int DOCUMENT_API_LEVEL = 4;
+    public static final int DOCUMENT_API_LEVEL = 5;
 
     // We also keep a more fine-grained BUILD number, exposed as
     // ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD
-    static final float BUILD = 0.7f;
+    static final float BUILD = 0.0f;
 
     private static final boolean UPDATE_VARIABLES_BEFORE_LAYOUT = false;
 
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 add9d5b..2025236 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -30,6 +30,7 @@
 import com.android.internal.widget.remotecompose.core.operations.DataListIds;
 import com.android.internal.widget.remotecompose.core.operations.DataMapIds;
 import com.android.internal.widget.remotecompose.core.operations.DataMapLookup;
+import com.android.internal.widget.remotecompose.core.operations.DebugMessage;
 import com.android.internal.widget.remotecompose.core.operations.DrawArc;
 import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
 import com.android.internal.widget.remotecompose.core.operations.DrawBitmapFontText;
@@ -231,6 +232,7 @@
     public static final int PATH_COMBINE = 175;
     public static final int HAPTIC_FEEDBACK = 177;
     public static final int CONDITIONAL_OPERATIONS = 178;
+    public static final int DEBUG_MESSAGE = 179;
 
     ///////////////////////////////////////// ======================
 
@@ -443,6 +445,7 @@
         map.put(PATH_COMBINE, PathCombine::read);
         map.put(HAPTIC_FEEDBACK, HapticFeedback::read);
         map.put(CONDITIONAL_OPERATIONS, ConditionalOperations::read);
+        map.put(DEBUG_MESSAGE, DebugMessage::read);
 
         //        map.put(ACCESSIBILITY_CUSTOM_ACTION, CoreSemantics::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 1f02668..a86b62e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -33,6 +33,7 @@
 import com.android.internal.widget.remotecompose.core.operations.DataListIds;
 import com.android.internal.widget.remotecompose.core.operations.DataMapIds;
 import com.android.internal.widget.remotecompose.core.operations.DataMapLookup;
+import com.android.internal.widget.remotecompose.core.operations.DebugMessage;
 import com.android.internal.widget.remotecompose.core.operations.DrawArc;
 import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
 import com.android.internal.widget.remotecompose.core.operations.DrawBitmapFontText;
@@ -1332,7 +1333,7 @@
      * @return the nan id of float
      */
     public float reserveFloatVariable() {
-        int id = mRemoteComposeState.cacheFloat(0f);
+        int id = mRemoteComposeState.nextId();
         return Utils.asNan(id);
     }
 
@@ -1870,6 +1871,46 @@
     }
 
     /**
+     * Add a scroll modifier
+     *
+     * @param direction HORIZONTAL(0) or VERTICAL(1)
+     * @param positionId the position id as a NaN
+     */
+    public void addModifierScroll(int direction, float positionId) {
+        float max = this.reserveFloatVariable();
+        float notchMax = this.reserveFloatVariable();
+        float touchExpressionDirection =
+                direction != 0 ? RemoteContext.FLOAT_TOUCH_POS_X : RemoteContext.FLOAT_TOUCH_POS_Y;
+
+        ScrollModifierOperation.apply(mBuffer, direction, positionId, max, notchMax);
+        this.addTouchExpression(
+                positionId,
+                0f,
+                0f,
+                max,
+                0f,
+                3,
+                new float[] {
+                    touchExpressionDirection, -1, MUL,
+                },
+                TouchExpression.STOP_GENTLY,
+                null,
+                null);
+        ContainerEnd.apply(mBuffer);
+    }
+
+    /**
+     * Add a scroll modifier
+     *
+     * @param direction HORIZONTAL(0) or VERTICAL(1)
+     */
+    public void addModifierScroll(int direction) {
+        float max = this.reserveFloatVariable();
+        ScrollModifierOperation.apply(mBuffer, direction, 0f, max, 0f);
+        ContainerEnd.apply(mBuffer);
+    }
+
+    /**
      * Add a background modifier of provided color
      *
      * @param color the color of the background
@@ -2464,4 +2505,15 @@
     public void addConditionalOperations(byte type, float a, float b) {
         ConditionalOperations.apply(mBuffer, type, a, b);
     }
+
+    /**
+     * Add a debug message
+     *
+     * @param textId text id
+     * @param value
+     * @param flags
+     */
+    public void addDebugMessage(int textId, float value, int flags) {
+        DebugMessage.apply(mBuffer, textId, value, flags);
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DebugMessage.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DebugMessage.java
new file mode 100644
index 0000000..c27bd8b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DebugMessage.java
@@ -0,0 +1,157 @@
+/*
+ * 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;
+
+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.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+
+import java.util.List;
+
+/**
+ * This prints debugging message useful for debugging. It should not be use in production documents
+ */
+public class DebugMessage extends Operation implements VariableSupport {
+    private static final int OP_CODE = Operations.DEBUG_MESSAGE;
+    private static final String CLASS_NAME = "DebugMessage";
+    int mTextID;
+    float mFloatValue;
+    float mOutFloatValue;
+    int mFlags = 0;
+
+    public DebugMessage(int textID, float value, int flags) {
+        mTextID = textID;
+        mFloatValue = value;
+        mFlags = flags;
+    }
+
+    @Override
+    public void updateVariables(@NonNull RemoteContext context) {
+        System.out.println("Debug message : updateVariables ");
+        mOutFloatValue =
+                Float.isNaN(mFloatValue)
+                        ? context.getFloat(Utils.idFromNan(mFloatValue))
+                        : mFloatValue;
+        System.out.println(
+                "Debug message : updateVariables "
+                        + Utils.floatToString(mFloatValue, mOutFloatValue));
+    }
+
+    @Override
+    public void registerListening(@NonNull RemoteContext context) {
+        System.out.println("Debug message : registerListening ");
+
+        if (Float.isNaN(mFloatValue)) {
+            System.out.println("Debug message : registerListening " + mFloatValue);
+            context.listensTo(Utils.idFromNan(mFloatValue), this);
+        }
+    }
+
+    @Override
+    public void write(@NonNull WireBuffer buffer) {
+        apply(buffer, mTextID, mFloatValue, mFlags);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "DebugMessage "
+                + mTextID
+                + ", "
+                + Utils.floatToString(mFloatValue, mOutFloatValue)
+                + ", "
+                + mFlags;
+    }
+
+    /**
+     * 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 text = buffer.readInt();
+        float floatValue = buffer.readFloat();
+        int flags = buffer.readInt();
+        DebugMessage op = new DebugMessage(text, floatValue, flags);
+        operations.add(op);
+    }
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
+    public static int id() {
+        return OP_CODE;
+    }
+
+    /**
+     * Writes out the operation to the buffer
+     *
+     * @param buffer write the command to the buffer
+     * @param textID id of the text
+     * @param value value to print
+     * @param flags flags to print
+     */
+    public static void apply(@NonNull WireBuffer buffer, int textID, float value, int flags) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(textID);
+        buffer.writeFloat(value);
+        buffer.writeInt(flags);
+    }
+
+    /**
+     * 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("DebugMessage Operations", id(), CLASS_NAME)
+                .description("Print debugging messages")
+                .field(DocumentedOperation.INT, "textId", "test to print")
+                .field(DocumentedOperation.FLOAT, "value", "value of a float to print")
+                .field(DocumentedOperation.INT, "flags", "print additional information");
+    }
+
+    @Override
+    public void apply(@NonNull RemoteContext context) {
+        String str = context.getText(mTextID);
+        System.out.println("Debug message : " + str + " " + mOutFloatValue + " " + mFlags);
+    }
+
+    @NonNull
+    @Override
+    public String deepToString(@NonNull String indent) {
+        return indent + toString();
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
index dee79a4..67d3a65 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
@@ -266,10 +266,12 @@
             case TIME_FROM_NOW_SEC:
             case TIME_FROM_ARG_SEC:
                 ctx.loadFloat(mId, (delta) * 1E-3f);
+                ctx.needsRepaint();
                 break;
             case TIME_FROM_ARG_MIN:
             case TIME_FROM_NOW_MIN:
                 ctx.loadFloat(mId, (float) (delta * 1E-3 / 60));
+                ctx.needsRepaint();
                 break;
             case TIME_FROM_ARG_HR:
             case TIME_FROM_NOW_HR:
@@ -298,6 +300,7 @@
                 break;
             case TIME_FROM_LOAD_SEC:
                 ctx.loadFloat(mId, (value - load_time) * 1E-3f);
+                ctx.needsRepaint();
                 break;
         }
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
index f246729..3e5dff8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
@@ -494,7 +494,7 @@
         mTouchUpTime = context.getAnimationTime();
 
         float dest = getStopPosition(value, slope);
-        float time = mMaxTime * Math.abs(dest - value) / (2 * mMaxVelocity);
+        float time = Math.min(2, mMaxTime * Math.abs(dest - value) / (2 * mMaxVelocity));
         mEasyTouch.config(value, dest, slope, time, mMaxAcceleration, mMaxVelocity, null);
         mEasingToStop = true;
         context.needsRepaint();
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 17f4fc8..e76fb06 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
@@ -42,6 +42,9 @@
 public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachStateChangeListener {
 
     static final boolean USE_VIEW_AREA_CLICK = true; // Use views to represent click areas
+    static final float DEFAULT_FRAME_RATE = 60f;
+    static final float POST_TO_NEXT_FRAME_THRESHOLD = 60f;
+
     RemoteComposeDocument mDocument = null;
     int mTheme = Theme.LIGHT;
     boolean mInActionDown = false;
@@ -53,9 +56,11 @@
     long mStart = System.nanoTime();
 
     long mLastFrameDelay = 1;
-    float mMaxFrameRate = 60f; // frames per seconds
+    float mMaxFrameRate = DEFAULT_FRAME_RATE; // frames per seconds
     long mMaxFrameDelay = (long) (1000 / mMaxFrameRate);
 
+    long mLastFrameCall = System.currentTimeMillis();
+
     private Choreographer mChoreographer;
     private Choreographer.FrameCallback mFrameCallback =
             new Choreographer.FrameCallback() {
@@ -100,6 +105,7 @@
 
     public void setDocument(RemoteComposeDocument value) {
         mDocument = value;
+        mMaxFrameRate = DEFAULT_FRAME_RATE;
         mDocument.initializeContext(mARContext);
         mDisable = false;
         mARContext.setDocLoadTime();
@@ -546,8 +552,25 @@
             }
             int nextFrame = mDocument.needsRepaint();
             if (nextFrame > 0) {
-                mLastFrameDelay = Math.max(mMaxFrameDelay, nextFrame);
+                if (mMaxFrameRate >= POST_TO_NEXT_FRAME_THRESHOLD) {
+                    mLastFrameDelay = nextFrame;
+                } else {
+                    mLastFrameDelay = Math.max(mMaxFrameDelay, nextFrame);
+                }
                 if (mChoreographer != null) {
+                    if (mDebug == 1) {
+                        System.err.println(
+                                "RC : POST CHOREOGRAPHER WITH "
+                                        + mLastFrameDelay
+                                        + " (nextFrame was "
+                                        + nextFrame
+                                        + ", max delay "
+                                        + mMaxFrameDelay
+                                        + ", "
+                                        + " max framerate is "
+                                        + mMaxFrameRate
+                                        + ")");
+                    }
                     mChoreographer.postFrameCallbackDelayed(mFrameCallback, mLastFrameDelay);
                 }
                 if (!mARContext.useChoreographer()) {
@@ -567,6 +590,16 @@
             mDisable = true;
             invalidate();
         }
+        if (mDebug == 1) {
+            long frameDelay = System.currentTimeMillis() - mLastFrameCall;
+            System.err.println(
+                    "RC : Delay since last frame "
+                            + frameDelay
+                            + " ms ("
+                            + (1000f / (float) frameDelay)
+                            + " fps)");
+            mLastFrameCall = System.currentTimeMillis();
+        }
     }
 
     private void drawDisable(Canvas canvas) {
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index a0c8f30..36bda61 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -73,6 +73,7 @@
     jmethodID mExecTransact;
     jmethodID mGetInterfaceDescriptor;
     jmethodID mTransactionCallback;
+    jmethodID mGetExtension;
 
     // Object state.
     jfieldID mObject;
@@ -488,8 +489,12 @@
             if (mVintf) {
                 ::android::internal::Stability::markVintf(b.get());
             }
-            if (mExtension != nullptr) {
-                b.get()->setExtension(mExtension);
+            if (mSetExtensionCalled) {
+                jobject javaIBinderObject = env->CallObjectMethod(obj, gBinderOffsets.mGetExtension);
+                sp<IBinder> extensionFromJava = ibinderForJavaObject(env, javaIBinderObject);
+                if (extensionFromJava != nullptr) {
+                    b.get()->setExtension(extensionFromJava);
+                }
             }
             mBinder = b;
             ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n",
@@ -515,21 +520,12 @@
         mVintf = false;
     }
 
-    sp<IBinder> getExtension() {
-        AutoMutex _l(mLock);
-        sp<JavaBBinder> b = mBinder.promote();
-        if (b != nullptr) {
-            return b.get()->getExtension();
-        }
-        return mExtension;
-    }
-
     void setExtension(const sp<IBinder>& extension) {
         AutoMutex _l(mLock);
-        mExtension = extension;
+        mSetExtensionCalled = true;
         sp<JavaBBinder> b = mBinder.promote();
         if (b != nullptr) {
-            b.get()->setExtension(mExtension);
+            b.get()->setExtension(extension);
         }
     }
 
@@ -541,8 +537,7 @@
     // is too much binder state here, we can think about making JavaBBinder an
     // sp here (avoid recreating it)
     bool            mVintf = false;
-
-    sp<IBinder>     mExtension;
+    bool            mSetExtensionCalled = false;
 };
 
 // ----------------------------------------------------------------------------
@@ -1254,10 +1249,6 @@
     return IPCThreadState::self()->blockUntilThreadAvailable();
 }
 
-static jobject android_os_Binder_getExtension(JNIEnv* env, jobject obj) {
-    JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject);
-    return javaObjectForIBinder(env, jbh->getExtension());
-}
 
 static void android_os_Binder_setExtension(JNIEnv* env, jobject obj, jobject extensionObject) {
     JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject);
@@ -1300,8 +1291,7 @@
     { "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
     { "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
     { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable },
-    { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension },
-    { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
+    { "setExtensionNative", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
 };
 // clang-format on
 
@@ -1318,6 +1308,8 @@
     gBinderOffsets.mTransactionCallback =
             GetStaticMethodIDOrDie(env, clazz, "transactionCallback", "(IIII)V");
     gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
+    gBinderOffsets.mGetExtension = GetMethodIDOrDie(env, clazz, "getExtension",
+                                                        "()Landroid/os/IBinder;");
 
     return RegisterMethodsOrDie(
         env, kBinderPathName,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e16ce98..9e02004 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5394,13 +5394,13 @@
          corresponding permission such as {@link #HEAD_TRACKING} or
          {@link #FACE_TRACKING} for the data being accessed.
 
-         <p>Protection level: normal|appop
+         <p>Protection level: signature|privileged
 
          @SystemApi
          @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
          @hide -->
     <permission android:name="android.permission.XR_TRACKING_IN_BACKGROUND"
-                android:protectionLevel="normal|appop"
+                android:protectionLevel="signature|privileged"
                 android:description="@string/permdesc_xr_tracking_in_background"
                 android:label="@string/permlab_xr_tracking_in_background"
                 android:featureFlag="android.xr.xr_manifest_entries" />
diff --git a/core/res/res/drawable/notification_progress.xml b/core/res/res/drawable/notification_progress.xml
index ff5450e..92a0a6a 100644
--- a/core/res/res/drawable/notification_progress.xml
+++ b/core/res/res/drawable/notification_progress.xml
@@ -19,12 +19,9 @@
           android:gravity="center_vertical|fill_horizontal">
         <com.android.internal.widget.NotificationProgressDrawable
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:segSegGap="@dimen/notification_progress_segSeg_gap"
-            android:segPointGap="@dimen/notification_progress_segPoint_gap">
+            android:layout_height="wrap_content">
             <segments
                 android:color="?attr/colorProgressBackgroundNormal"
-                android:minWidth="@dimen/notification_progress_segments_min_width"
                 android:height="@dimen/notification_progress_segments_height"
                 android:fadedHeight="@dimen/notification_progress_segments_faded_height"
                 android:cornerRadius="@dimen/notification_progress_segments_corner_radius"/>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d2c993ae..647e3dc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5573,6 +5573,14 @@
 
     <!-- @hide internal use only -->
     <declare-styleable name="NotificationProgressBar">
+        <!-- Minimum required drawing width for segments. The drawing width refers to the width
+             after the original segments have been adjusted for the neighboring Points and gaps.
+             This is enforced by stretching the segments that are too short. -->
+        <attr name="segMinWidth" format="dimension" />
+        <!-- The gap between two segments. -->
+        <attr name="segSegGap" format="dimension" />
+        <!-- The gap between a segment and a point. -->
+        <attr name="segPointGap" format="dimension" />
         <!-- Draws the tracker on a NotificationProgressBar. -->
         <attr name="tracker" format="reference" />
         <!-- Height of the tracker. -->
@@ -7580,25 +7588,9 @@
     <!-- NotificationProgressDrawable class -->
     <!-- ================================== -->
 
-    <!-- Drawable used to render a notification progress bar, with segments and points. -->
-    <!-- @hide internal use only -->
-    <declare-styleable name="NotificationProgressDrawable">
-        <!-- The gap between two segments. -->
-        <attr name="segSegGap" format="dimension" />
-        <!-- The gap between a segment and a point. -->
-        <attr name="segPointGap" format="dimension" />
-    </declare-styleable>
-
     <!-- Used to config the segments of a NotificationProgressDrawable. -->
     <!-- @hide internal use only -->
     <declare-styleable name="NotificationProgressDrawableSegments">
-        <!-- 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
-         the original segments have been adjusted for the neighboring Points and gaps. This is
-         enforced by stretching the segments that are too short. -->
-        <attr name="minWidth" />
         <!-- Height of the solid segments. -->
         <attr name="height" />
         <!-- Height of the faded segments. -->
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 73681d2..8f13ee1 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -503,6 +503,9 @@
     <style name="Widget.Material.Notification.ProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal" />
 
     <style name="Widget.Material.Notification.NotificationProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal">
+        <item name="segMinWidth">@dimen/notification_progress_segments_min_width</item>
+        <item name="segSegGap">@dimen/notification_progress_segSeg_gap</item>
+        <item name="segPointGap">@dimen/notification_progress_segPoint_gap</item>
         <item name="progressDrawable">@drawable/notification_progress</item>
         <item name="trackerHeight">@dimen/notification_progress_tracker_height</item>
     </style>
diff --git a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
index 62d89f6..146b386 100644
--- a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
+++ b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
@@ -16,19 +16,37 @@
 
 package android.app;
 
+import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.CATEGORY_INFO;
+import static android.content.Intent.CATEGORY_LAUNCHER;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.os.storage.VolumeInfo.STATE_MOUNTED;
 import static android.os.storage.VolumeInfo.STATE_UNMOUNTED;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -44,6 +62,7 @@
 
 import junit.framework.TestCase;
 
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mockito;
 import org.xmlpull.v1.XmlPullParser;
 
@@ -102,14 +121,14 @@
         sVolumes.add(sPrivateUnmountedVol);
     }
 
-    private static final class MockedApplicationPackageManager extends ApplicationPackageManager {
+    public static class MockedApplicationPackageManager extends ApplicationPackageManager {
         private boolean mForceAllowOnExternal = false;
         private boolean mAllow3rdPartyOnInternal = true;
         private HashMap<ApplicationInfo, Resources> mResourcesMap;
 
         public MockedApplicationPackageManager() {
             super(null, null);
-            mResourcesMap = new HashMap<ApplicationInfo, Resources>();
+            mResourcesMap = new HashMap<>();
         }
 
         public void setForceAllowOnExternal(boolean forceAllowOnExternal) {
@@ -153,7 +172,7 @@
     }
 
     private StorageManager getMockedStorageManager() {
-        StorageManager storageManager = Mockito.mock(StorageManager.class);
+        StorageManager storageManager = mock(StorageManager.class);
         Mockito.when(storageManager.getVolumes()).thenReturn(sVolumes);
         Mockito.when(storageManager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL))
                 .thenReturn(sInternalVol);
@@ -190,7 +209,7 @@
         sysAppInfo.flags = ApplicationInfo.FLAG_SYSTEM;
 
         StorageManager storageManager = getMockedStorageManager();
-        IPackageManager pm = Mockito.mock(IPackageManager.class);
+        IPackageManager pm = mock(IPackageManager.class);
 
         MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
 
@@ -220,7 +239,7 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         StorageManager storageManager = getMockedStorageManager();
 
-        IPackageManager pm = Mockito.mock(IPackageManager.class);
+        IPackageManager pm = mock(IPackageManager.class);
         Mockito.when(pm.isPackageDeviceAdminOnAnyUser(Mockito.anyString())).thenReturn(false);
 
         MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
@@ -249,7 +268,7 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         StorageManager storageManager = getMockedStorageManager();
 
-        IPackageManager pm = Mockito.mock(IPackageManager.class);
+        IPackageManager pm = mock(IPackageManager.class);
 
         MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
         appPkgMgr.setForceAllowOnExternal(true);
@@ -291,15 +310,15 @@
 
     public void testExtractPackageItemInfoAttributes_noMetaData() {
         final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
-        final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class);
+        final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class);
         assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, null,
                 new int[]{})).isNull();
     }
 
     public void testExtractPackageItemInfoAttributes_noParser() {
         final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
-        final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class);
-        final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class);
+        final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class);
+        final ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
         when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo);
         assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, null,
                 new int[]{})).isNull();
@@ -307,8 +326,8 @@
 
     public void testExtractPackageItemInfoAttributes_noMetaDataXml() {
         final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
-        final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class);
-        final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class);
+        final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class);
+        final ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
         when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo);
         when(packageItemInfo.loadXmlMetaData(any(), any())).thenReturn(null);
         assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, null,
@@ -318,9 +337,9 @@
     public void testExtractPackageItemInfoAttributes_nonMatchingRootTag() throws Exception {
         final String rootTag = "rootTag";
         final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
-        final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class);
-        final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class);
-        final XmlResourceParser parser = Mockito.mock(XmlResourceParser.class);
+        final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class);
+        final ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
+        final XmlResourceParser parser = mock(XmlResourceParser.class);
 
         when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo);
         packageItemInfo.metaData = new Bundle();
@@ -334,11 +353,11 @@
     public void testExtractPackageItemInfoAttributes_successfulExtraction() throws Exception {
         final String rootTag = "rootTag";
         final MockedApplicationPackageManager appPkgMgr = new MockedApplicationPackageManager();
-        final PackageItemInfo packageItemInfo = Mockito.mock(PackageItemInfo.class);
-        final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class);
-        final XmlResourceParser parser = Mockito.mock(XmlResourceParser.class);
-        final Resources resources = Mockito.mock(Resources.class);
-        final TypedArray attributes = Mockito.mock(TypedArray.class);
+        final PackageItemInfo packageItemInfo = mock(PackageItemInfo.class);
+        final ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
+        final XmlResourceParser parser = mock(XmlResourceParser.class);
+        final Resources resources = mock(Resources.class);
+        final TypedArray attributes = mock(TypedArray.class);
 
         when(packageItemInfo.getApplicationInfo()).thenReturn(applicationInfo);
         packageItemInfo.metaData = new Bundle();
@@ -351,4 +370,123 @@
         assertThat(appPkgMgr.extractPackageItemInfoAttributes(packageItemInfo, null, rootTag,
                 new int[]{})).isEqualTo(attributes);
     }
+
+    public void testGetLaunchIntentForPackage_categoryInfoActivity_returnsIt() throws Exception {
+        String pkg = "com.some.package";
+        int userId = 42;
+        ResolveInfo categoryInfoResolveInfo = new ResolveInfo();
+        categoryInfoResolveInfo.activityInfo = new ActivityInfo();
+        categoryInfoResolveInfo.activityInfo.packageName = pkg;
+        categoryInfoResolveInfo.activityInfo.name = "activity";
+        Intent baseIntent = new Intent(ACTION_MAIN).setPackage(pkg);
+
+        final MockedApplicationPackageManager pm = spy(new MockedApplicationPackageManager());
+        doReturn(userId).when(pm).getUserId();
+        doReturn(List.of(categoryInfoResolveInfo))
+                .when(pm).queryIntentActivitiesAsUser(
+                        eqIntent(new Intent(baseIntent).addCategory(CATEGORY_INFO)),
+                        any(ResolveInfoFlags.class),
+                        anyInt());
+        doReturn(
+                List.of())
+                .when(pm).queryIntentActivitiesAsUser(
+                        eqIntent(new Intent(baseIntent).addCategory(CATEGORY_LAUNCHER)),
+                        any(ResolveInfoFlags.class),
+                        anyInt());
+
+        Intent intent = pm.getLaunchIntentForPackage(pkg, true);
+
+        assertThat(intent).isNotNull();
+        assertThat(intent.getComponent()).isEqualTo(new ComponentName(pkg, "activity"));
+        assertThat(intent.getCategories()).containsExactly(CATEGORY_INFO);
+        assertThat(intent.getFlags()).isEqualTo(FLAG_ACTIVITY_NEW_TASK);
+        verify(pm).queryIntentActivitiesAsUser(
+                eqIntent(new Intent(ACTION_MAIN).addCategory(CATEGORY_INFO).setPackage(pkg)),
+                eqResolveInfoFlags(MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE),
+                eq(userId));
+    }
+
+    public void testGetLaunchIntentForPackage_categoryLauncherActivity_returnsIt() {
+        String pkg = "com.some.package";
+        int userId = 42;
+        ResolveInfo categoryLauncherResolveInfo1 = new ResolveInfo();
+        categoryLauncherResolveInfo1.activityInfo = new ActivityInfo();
+        categoryLauncherResolveInfo1.activityInfo.packageName = pkg;
+        categoryLauncherResolveInfo1.activityInfo.name = "activity1";
+        ResolveInfo categoryLauncherResolveInfo2 = new ResolveInfo();
+        categoryLauncherResolveInfo2.activityInfo = new ActivityInfo();
+        categoryLauncherResolveInfo2.activityInfo.packageName = pkg;
+        categoryLauncherResolveInfo2.activityInfo.name = "activity2";
+        Intent baseIntent = new Intent(ACTION_MAIN).setPackage(pkg);
+
+        final MockedApplicationPackageManager pm = spy(new MockedApplicationPackageManager());
+        doReturn(userId).when(pm).getUserId();
+        doReturn(List.of())
+                .when(pm).queryIntentActivitiesAsUser(
+                        eqIntent(new Intent(baseIntent).addCategory(CATEGORY_INFO)),
+                        any(ResolveInfoFlags.class),
+                        anyInt());
+        doReturn(
+                List.of(categoryLauncherResolveInfo1, categoryLauncherResolveInfo2))
+                .when(pm).queryIntentActivitiesAsUser(
+                        eqIntent(new Intent(baseIntent).addCategory(CATEGORY_LAUNCHER)),
+                        any(ResolveInfoFlags.class),
+                        anyInt());
+
+        Intent intent = pm.getLaunchIntentForPackage(pkg, true);
+
+        assertThat(intent).isNotNull();
+        assertThat(intent.getComponent()).isEqualTo(new ComponentName(pkg, "activity1"));
+        assertThat(intent.getCategories()).containsExactly(CATEGORY_LAUNCHER);
+        assertThat(intent.getFlags()).isEqualTo(FLAG_ACTIVITY_NEW_TASK);
+    }
+
+    public void testGetLaunchIntentForPackage_noSuitableActivity_returnsNull() throws Exception {
+        String pkg = "com.some.package";
+        int userId = 42;
+
+        final MockedApplicationPackageManager pm = spy(new MockedApplicationPackageManager());
+        doReturn(userId).when(pm).getUserId();
+        doReturn(List.of())
+                .when(pm).queryIntentActivitiesAsUser(
+                        any(),
+                        any(ResolveInfoFlags.class),
+                        anyInt());
+
+        Intent intent = pm.getLaunchIntentForPackage(pkg, true);
+
+        assertThat(intent).isNull();
+    }
+
+    /** Equality check for intents -- ignoring extras */
+    private static Intent eqIntent(Intent wanted) {
+        return argThat(
+                new ArgumentMatcher<>() {
+                    @Override
+                    public boolean matches(Intent argument) {
+                        return wanted.filterEquals(argument)
+                                && wanted.getFlags() == argument.getFlags();
+                    }
+
+                    @Override
+                    public String toString() {
+                        return wanted.toString();
+                    }
+                });
+    }
+
+    private static ResolveInfoFlags eqResolveInfoFlags(long flagsWanted) {
+        return argThat(
+                new ArgumentMatcher<>() {
+                    @Override
+                    public boolean matches(ResolveInfoFlags argument) {
+                        return argument.getValue() == flagsWanted;
+                    }
+
+                    @Override
+                    public String toString() {
+                        return String.valueOf(flagsWanted);
+                    }
+                });
+    }
 }
diff --git a/core/tests/coretests/src/android/content/pm/UserInfoTest.java b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
index edeea6d..c84c215 100644
--- a/core/tests/coretests/src/android/content/pm/UserInfoTest.java
+++ b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
@@ -16,19 +16,44 @@
 
 package android.content.pm;
 
+import static android.content.pm.UserInfo.FLAG_DEMO;
+import static android.content.pm.UserInfo.FLAG_FULL;
+import static android.content.pm.UserInfo.FLAG_GUEST;
+import static android.content.pm.UserInfo.FLAG_MAIN;
+import static android.content.pm.UserInfo.FLAG_PROFILE;
+import static android.content.pm.UserInfo.FLAG_SYSTEM;
+import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
+import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
+import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import android.content.pm.UserInfo.UserInfoFlag;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class UserInfoTest {
+public final class UserInfoTest {
+
+    @Rule
+    public final SetFlagsRule flags =
+            new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+
+    @Rule public final Expect expect = Expect.create();
+
     @Test
     public void testSimple() throws Exception {
         final UserInfo ui = new UserInfo(10, "Test", UserInfo.FLAG_GUEST);
@@ -56,9 +81,6 @@
         assertThat(ui.isInitialized()).isEqualTo(false);
         assertThat(ui.isFull()).isEqualTo(false);
         assertThat(ui.isMain()).isEqualTo(false);
-
-        // Derived dynamically
-        assertThat(ui.canHaveProfile()).isEqualTo(false);
     }
 
     @Test
@@ -68,4 +90,64 @@
         assertThat(ui.toString()).isNotEmpty();
         assertThat(ui.toFullString()).isNotEmpty();
     }
+
+    @Test
+    @DisableFlags(android.multiuser.Flags.FLAG_PROFILES_FOR_ALL)
+    public void testCanHaveProfile_flagProfilesForAllDisabled() {
+        expectCannotHaveProfile("non-full user", createTestUserInfo(/* flags= */ 0));
+        expectCannotHaveProfile("guest user", createTestUserInfo(FLAG_FULL | FLAG_GUEST));
+        expectCanHaveProfile("main user", createTestUserInfo(FLAG_FULL | FLAG_MAIN));
+        expectCannotHaveProfile("non-main user", createTestUserInfo(FLAG_FULL));
+        expectCannotHaveProfile("demo user", createTestUserInfo(FLAG_FULL | FLAG_DEMO));
+        expectCannotHaveProfile("restricted user",
+                createTestUserInfo(USER_TYPE_FULL_RESTRICTED, FLAG_FULL));
+        expectCannotHaveProfile("profile user", createTestUserInfo(FLAG_PROFILE));
+        expectCanHaveProfile("(full) system user that's also main user",
+                createTestUserInfo(USER_TYPE_FULL_SYSTEM, FLAG_FULL | FLAG_SYSTEM | FLAG_MAIN));
+        expectCannotHaveProfile("headless system user that's not main user",
+                createTestUserInfo(USER_TYPE_SYSTEM_HEADLESS, FLAG_SYSTEM));
+    }
+
+    @Test
+    @EnableFlags(android.multiuser.Flags.FLAG_PROFILES_FOR_ALL)
+    public void testCanHaveProfile_flagProfilesForAllEnabled() {
+        expectCannotHaveProfile("non-full user", createTestUserInfo(/* flags= */ 0));
+        expectCannotHaveProfile("guest user", createTestUserInfo(FLAG_FULL | FLAG_GUEST));
+        expectCanHaveProfile("main user", createTestUserInfo(FLAG_FULL | FLAG_MAIN));
+        expectCanHaveProfile("non-main user", createTestUserInfo(FLAG_FULL));
+        expectCannotHaveProfile("demo user", createTestUserInfo(FLAG_FULL | FLAG_DEMO));
+        expectCannotHaveProfile("restricted user",
+                createTestUserInfo(USER_TYPE_FULL_RESTRICTED, FLAG_FULL));
+        expectCannotHaveProfile("profile user", createTestUserInfo(FLAG_PROFILE));
+        expectCanHaveProfile("(full) system user that's also main user",
+                createTestUserInfo(USER_TYPE_FULL_SYSTEM, FLAG_FULL | FLAG_SYSTEM | FLAG_MAIN));
+        expectCannotHaveProfile("headless system user that's not main user",
+                createTestUserInfo(USER_TYPE_SYSTEM_HEADLESS, FLAG_SYSTEM));
+    }
+
+    /**
+     * Creates a new {@link UserInfo} with id {@code 10}, name {@code Test}, and the given
+     * {@code flags}.
+     */
+    private UserInfo createTestUserInfo(@UserInfoFlag int flags) {
+        return new UserInfo(10, "Test", flags);
+    }
+
+    /**
+     * Creates a new {@link UserInfo} with id {@code 10}, name {@code Test}, and the given
+     * {@code userType} and {@code flags}.
+     */
+    private UserInfo createTestUserInfo(String userType, @UserInfoFlag int flags) {
+        return new UserInfo(10, "Test", /* iconPath= */ null, flags, userType);
+    }
+
+    private void expectCanHaveProfile(String description, UserInfo user) {
+        expect.withMessage("canHaveProfile() on %s (%s)", description, user)
+                .that(user.canHaveProfile()).isTrue();
+    }
+
+    private void expectCannotHaveProfile(String description, UserInfo user) {
+        expect.withMessage("canHaveProfile() on %s (%s)", description, user)
+                .that(user.canHaveProfile()).isFalse();
+    }
 }
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index bb05910..3e652010 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -29,8 +29,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import java.nio.BufferOverflowException;
-import java.nio.ByteBuffer;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -418,63 +416,4 @@
         int binderEndPos = pA.dataPosition();
         assertTrue(pA.hasBinders(binderStartPos, binderEndPos - binderStartPos));
     }
-
-    private static final byte[] TEST_DATA = new byte[] {4, 8, 15, 16, 23, 42};
-
-    // Allow for some Parcel overhead
-    private static final int TEST_DATA_LENGTH = TEST_DATA.length + 100;
-
-    @Test
-    public void testMarshall_ByteBuffer_wrapped() {
-        ByteBuffer bb = ByteBuffer.allocate(TEST_DATA_LENGTH);
-        testMarshall_ByteBuffer(bb);
-    }
-
-    @Test
-    public void testMarshall_DirectByteBuffer() {
-        ByteBuffer bb = ByteBuffer.allocateDirect(TEST_DATA_LENGTH);
-        testMarshall_ByteBuffer(bb);
-    }
-
-    private void testMarshall_ByteBuffer(ByteBuffer bb) {
-        // Ensure that Parcel respects the starting offset by not starting at 0
-        bb.position(1);
-        bb.mark();
-
-        // Parcel test data, then marshall into the ByteBuffer
-        Parcel p1 = Parcel.obtain();
-        p1.writeByteArray(TEST_DATA);
-        p1.marshall(bb);
-        p1.recycle();
-
-        assertTrue(bb.position() > 1);
-        bb.reset();
-
-        // Unmarshall test data into a new Parcel
-        Parcel p2 = Parcel.obtain();
-        bb.reset();
-        p2.unmarshall(bb);
-        assertTrue(bb.position() > 1);
-        p2.setDataPosition(0);
-        byte[] marshalled = p2.marshall();
-
-        bb.reset();
-        for (int i = 0; i < TEST_DATA.length; i++) {
-            assertEquals(bb.get(), marshalled[i]);
-        }
-
-        byte[] testDataCopy = new byte[TEST_DATA.length];
-        p2.setDataPosition(0);
-        p2.readByteArray(testDataCopy);
-        for (int i = 0; i < TEST_DATA.length; i++) {
-            assertEquals(TEST_DATA[i], testDataCopy[i]);
-        }
-
-        // Test that overflowing the buffer throws an exception
-        bb.reset();
-        // Leave certainly not enough room for the test data
-        bb.limit(bb.position() + TEST_DATA.length - 1);
-        assertThrows(BufferOverflowException.class, () -> p2.marshall(bb));
-        p2.recycle();
-    }
 }
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index b6a1501..19455a3 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -211,3 +211,10 @@
     description: "Makes the split divider snap 'magnetically' to available snap points during drag"
     bug: "383631946"
 }
+
+flag {
+    name: "enable_dynamic_insets_for_app_launch"
+    namespace: "multitasking"
+    description: "Enables dynamic insets for app launch so the window is properly cropped"
+    bug: "336511494"
+}
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 14c152102..90011f4 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
@@ -24,6 +24,9 @@
 import android.graphics.Rect
 import android.os.Handler
 import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
 import android.view.IWindowManager
 import android.view.MotionEvent
 import android.view.View
@@ -36,6 +39,7 @@
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.protolog.ProtoLog
 import com.android.internal.statusbar.IStatusBarService
+import com.android.wm.shell.Flags
 import com.android.wm.shell.R
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.bubbles.Bubble
@@ -64,6 +68,10 @@
 import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation
 import com.android.wm.shell.shared.bubbles.DeviceConfig
+import com.android.wm.shell.shared.bubbles.DragZone
+import com.android.wm.shell.shared.bubbles.DragZoneFactory
+import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker.SplitScreenMode
+import com.android.wm.shell.shared.bubbles.DraggedObject
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
@@ -88,6 +96,8 @@
         const val SCREEN_HEIGHT = 1000
     }
 
+    @get:Rule val setFlagsRule = SetFlagsRule()
+
     @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this)
 
     private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -101,6 +111,7 @@
     private lateinit var bgExecutor: TestShellExecutor
     private lateinit var bubbleLogger: BubbleLogger
     private lateinit var testBubblesList: MutableList<Bubble>
+    private lateinit var dragZoneFactory: DragZoneFactory
 
     @Before
     fun setUp() {
@@ -134,6 +145,10 @@
         whenever(bubbleData.bubbles).thenReturn(testBubblesList)
         whenever(bubbleData.hasBubbles()).thenReturn(!testBubblesList.isEmpty())
 
+        dragZoneFactory = DragZoneFactory(context, deviceConfig,
+            { SplitScreenMode.UNSUPPORTED },
+            { false })
+
         bubbleController =
             createBubbleController(
                 bubbleData,
@@ -280,6 +295,7 @@
         assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
     }
 
+    @DisableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_BUBBLE_ANYTHING)
     @Test
     fun testEventLogging_dragExpandedViewLeft() {
         val bubble = createBubble("first")
@@ -287,7 +303,7 @@
 
         getInstrumentation().runOnMainSync {
             bubbleBarLayerView.showExpandedView(bubble)
-            bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true)
+            bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */)
         }
         waitForExpandedViewAnimation()
 
@@ -305,6 +321,7 @@
         assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
     }
 
+    @DisableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_BUBBLE_ANYTHING)
     @Test
     fun testEventLogging_dragExpandedViewRight() {
         val bubble = createBubble("first")
@@ -312,7 +329,7 @@
 
         getInstrumentation().runOnMainSync {
             bubbleBarLayerView.showExpandedView(bubble)
-            bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true)
+            bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */)
         }
         waitForExpandedViewAnimation()
 
@@ -330,6 +347,76 @@
         assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
     }
 
+    @EnableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE)
+    @Test
+    fun testEventLogging_dragExpandedViewLeft_bubbleAnything() {
+        val bubble = createBubble("first")
+        bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+
+        getInstrumentation().runOnMainSync {
+            bubbleBarLayerView.showExpandedView(bubble)
+            bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */)
+        }
+        waitForExpandedViewAnimation()
+
+        val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view)
+        assertThat(handleView).isNotNull()
+
+        val dragZones = dragZoneFactory.createSortedDragZones(
+            DraggedObject.ExpandedView(BubbleBarLocation.RIGHT))
+        val rightDragZone = dragZones.filterIsInstance<DragZone.Bubble.Right>().first()
+        val rightPoint = PointF(rightDragZone.bounds.centerX().toFloat(),
+            rightDragZone.bounds.centerY().toFloat())
+        val leftDragZone = dragZones.filterIsInstance<DragZone.Bubble.Left>().first()
+        val leftPoint = PointF(leftDragZone.bounds.centerX().toFloat(),
+            leftDragZone.bounds.centerY().toFloat())
+
+        // Drag from right to left
+        handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, rightPoint)
+        handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, leftPoint)
+        handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, leftPoint)
+
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(uiEventLoggerFake.logs[0].eventId)
+            .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW.id)
+        assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE)
+    @Test
+    fun testEventLogging_dragExpandedViewRight_bubbleAnything() {
+        val bubble = createBubble("first")
+        bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT
+
+        getInstrumentation().runOnMainSync {
+            bubbleBarLayerView.showExpandedView(bubble)
+            bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true /* visible */)
+        }
+        waitForExpandedViewAnimation()
+
+        val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view)
+        assertThat(handleView).isNotNull()
+
+        val dragZones = dragZoneFactory.createSortedDragZones(
+            DraggedObject.ExpandedView(BubbleBarLocation.LEFT))
+        val rightDragZone = dragZones.filterIsInstance<DragZone.Bubble.Right>().first()
+        val rightPoint = PointF(rightDragZone.bounds.centerX().toFloat(),
+            rightDragZone.bounds.centerY().toFloat())
+        val leftDragZone = dragZones.filterIsInstance<DragZone.Bubble.Left>().first()
+        val leftPoint = PointF(leftDragZone.bounds.centerX().toFloat(),
+            leftDragZone.bounds.centerY().toFloat())
+
+        // Drag from left to right
+        handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, leftPoint)
+        handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, rightPoint)
+        handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, rightPoint)
+
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(uiEventLoggerFake.logs[0].eventId)
+            .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW.id)
+        assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+    }
+
     @Test
     fun testUpdateExpandedView_updateLocation() {
         bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
@@ -385,7 +472,7 @@
                 bubbleLogger,
             )
         // Mark visible so we don't wait for task view before animations can start
-        bubbleBarExpandedView.onContentVisibilityChanged(true)
+        bubbleBarExpandedView.onContentVisibilityChanged(true /* visible */)
 
         val viewInfo = FakeBubbleFactory.createViewInfo(bubbleBarExpandedView)
         return FakeBubbleFactory.createChatBubble(context, key, viewInfo).also {
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
index 9bb51a8..ef30d89 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
@@ -34,6 +34,7 @@
         android:src="@drawable/bubble_ic_settings"/>
 
     <TextView
+        android:id="@+id/education_manage_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/bubble_popup_text_margin"
@@ -45,6 +46,7 @@
         android:text="@string/bubble_bar_education_manage_title"/>
 
     <TextView
+        android:id="@+id/education_manage_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/bubble_popup_text_margin"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
index 1616707..9076d6a 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -34,6 +34,7 @@
         android:src="@drawable/ic_floating_landscape"/>
 
     <TextView
+        android:id="@+id/education_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/bubble_popup_text_margin"
@@ -45,6 +46,7 @@
         android:text="@string/bubble_bar_education_stack_title"/>
 
     <TextView
+        android:id="@+id/education_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/bubble_popup_text_margin"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index 225303b..17ebac9 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -40,6 +40,7 @@
             android:tint="@color/bubbles_icon_tint"/>
 
         <TextView
+            android:id="@+id/manage_dismiss"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginStart="16dp"
@@ -67,6 +68,7 @@
             android:tint="@color/bubbles_icon_tint"/>
 
         <TextView
+            android:id="@+id/manage_dont_bubble"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginStart="16dp"
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
index 25b9f8c..f68afea 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.windowingModeToString;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.IntDef;
 import android.app.ActivityManager.RecentTaskInfo;
@@ -65,6 +66,11 @@
     private final int mDeskId;
 
     /**
+     * The ID of the display that desk with [mDeskId] is in.
+     */
+    private final int mDeskDisplayId;
+
+    /**
      * The type of this particular task info, can be one of TYPE_FULLSCREEN, TYPE_SPLIT or
      * TYPE_DESK.
      */
@@ -109,17 +115,19 @@
      * Create new for a stack of fullscreen tasks
      */
     public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) {
-        return new GroupedTaskInfo(/* deskId = */ -1, List.of(task), null, TYPE_FULLSCREEN,
-                /* minimizedFreeformTaskIds = */ null);
+        return new GroupedTaskInfo(/* deskId = */ -1, /* displayId = */ INVALID_DISPLAY,
+                List.of(task), null,
+                TYPE_FULLSCREEN, /* minimizedFreeformTaskIds = */ null);
     }
 
     /**
      * Create new for a pair of tasks in split screen
      */
     public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1,
-                    @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) {
-        return new GroupedTaskInfo(/* deskId = */ -1, List.of(task1, task2), splitBounds,
-                TYPE_SPLIT, /* minimizedFreeformTaskIds = */ null);
+            @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) {
+        return new GroupedTaskInfo(/* deskId = */ -1, /* displayId = */ INVALID_DISPLAY,
+                List.of(task1, task2),
+                splitBounds, TYPE_SPLIT, /* minimizedFreeformTaskIds = */ null);
     }
 
     /**
@@ -127,9 +135,11 @@
      */
     public static GroupedTaskInfo forDeskTasks(
             int deskId,
+            int deskDisplayId,
             @NonNull List<TaskInfo> tasks,
             @NonNull Set<Integer> minimizedFreeformTaskIds) {
-        return new GroupedTaskInfo(deskId, tasks, /* splitBounds = */ null, TYPE_DESK,
+        return new GroupedTaskInfo(deskId, deskDisplayId, tasks, /* splitBounds = */ null,
+                TYPE_DESK,
                 minimizedFreeformTaskIds.stream().mapToInt(i -> i).toArray());
     }
 
@@ -149,11 +159,13 @@
 
     private GroupedTaskInfo(
             int deskId,
+            int deskDisplayId,
             @NonNull List<TaskInfo> tasks,
             @Nullable SplitBounds splitBounds,
             @GroupType int type,
             @Nullable int[] minimizedFreeformTaskIds) {
         mDeskId = deskId;
+        mDeskDisplayId = deskDisplayId;
         mTasks = tasks;
         mGroupedTasks = null;
         mSplitBounds = splitBounds;
@@ -164,6 +176,7 @@
 
     private GroupedTaskInfo(@NonNull List<GroupedTaskInfo> groupedTasks) {
         mDeskId = -1;
+        mDeskDisplayId = INVALID_DISPLAY;
         mTasks = null;
         mGroupedTasks = groupedTasks;
         mSplitBounds = null;
@@ -185,6 +198,7 @@
 
     protected GroupedTaskInfo(@NonNull Parcel parcel) {
         mDeskId = parcel.readInt();
+        mDeskDisplayId = parcel.readInt();
         mTasks = new ArrayList();
         final int numTasks = parcel.readInt();
         for (int i = 0; i < numTasks; i++) {
@@ -295,6 +309,16 @@
     }
 
     /**
+     * Returns the ID of the display that hosts the desk represented by [mDeskId].
+     */
+    public int getDeskDisplayId() {
+        if (mType != TYPE_DESK) {
+            throw new IllegalStateException("No display ID for non desktop task");
+        }
+        return mDeskDisplayId;
+    }
+
+    /**
      * Get type of this recents entry. One of {@link GroupType}.
      * Note: This is deprecated, callers should use `isBaseType()` and not make assumptions about
      *       specific group types
@@ -323,6 +347,7 @@
         }
         GroupedTaskInfo other = (GroupedTaskInfo) obj;
         return mDeskId == other.mDeskId
+                && mDeskDisplayId == other.mDeskDisplayId
                 && mType == other.mType
                 && Objects.equals(mTasks, other.mTasks)
                 && Objects.equals(mGroupedTasks, other.mGroupedTasks)
@@ -332,7 +357,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mDeskId, mType, mTasks, mGroupedTasks, mSplitBounds,
+        return Objects.hash(mDeskId, mDeskDisplayId, mType, mTasks, mGroupedTasks, mSplitBounds,
                 Arrays.hashCode(mMinimizedTaskIds));
     }
 
@@ -345,6 +370,7 @@
                     .collect(Collectors.joining(",\n\t", "[\n\t", "\n]")));
         } else {
             taskString.append("Desk ID= ").append(mDeskId).append(", ");
+            taskString.append("Desk Display ID=").append(mDeskDisplayId).append(", ");
             taskString.append("Tasks=" + mTasks.stream()
                     .map(taskInfo -> getTaskInfoDumpString(taskInfo))
                     .collect(Collectors.joining(", ", "[", "]")));
@@ -377,6 +403,7 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(mDeskId);
+        parcel.writeInt(mDeskDisplayId);
         // We don't use the parcel list methods because we want to only write the TaskInfo state
         // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated
         final int tasksSize = mTasks != null ? mTasks.size() : 0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 7f8cfae..5e36a10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -332,7 +332,11 @@
 
         @Override
         public void onThresholdCrossed() {
-            BackAnimationController.this.onThresholdCrossed();
+            if (predictiveBackDelayWmTransition()) {
+                mShellExecutor.execute(BackAnimationController.this::onThresholdCrossed);
+            } else {
+                BackAnimationController.this.onThresholdCrossed();
+            }
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 426c3ee..290ef16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -27,6 +27,7 @@
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
+import static com.android.wm.shell.shared.TypefaceUtils.setTypeface;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
@@ -71,6 +72,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.AlphaOptimizedButton;
 import com.android.wm.shell.shared.TriangleShape;
+import com.android.wm.shell.shared.TypefaceUtils;
 import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 import com.android.wm.shell.taskview.TaskView;
 
@@ -551,6 +553,7 @@
         mManageButton = (AlphaOptimizedButton) LayoutInflater.from(ctw).inflate(
                 R.layout.bubble_manage_button, this /* parent */, false /* attach */);
         addView(mManageButton);
+        setTypeface(mManageButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE);
         mManageButton.setVisibility(visibility);
         setManageClickListener();
         post(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
index da6948d..92007a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
@@ -50,6 +50,7 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.shared.TriangleShape;
+import com.android.wm.shell.shared.TypefaceUtils;
 
 /**
  * Flyout view that appears as a 'chat bubble' alongside the bubble stack. The flyout can visually
@@ -165,8 +166,10 @@
         LayoutInflater.from(context).inflate(R.layout.bubble_flyout, this, true);
         mFlyoutTextContainer = findViewById(R.id.bubble_flyout_text_container);
         mSenderText = findViewById(R.id.bubble_flyout_name);
+        TypefaceUtils.setTypeface(mSenderText, TypefaceUtils.FontFamily.GSF_LABEL_LARGE);
         mSenderAvatar = findViewById(R.id.bubble_flyout_avatar);
         mMessageText = mFlyoutTextContainer.findViewById(R.id.bubble_flyout_text);
+        TypefaceUtils.setTypeface(mMessageText, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM);
 
         final Resources res = getResources();
         mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index 64f54b8..e901e0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -46,6 +46,7 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
+import com.android.wm.shell.shared.TypefaceUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -234,6 +235,10 @@
         setBackgroundColor(bgColor);
         mEmptyStateTitle.setTextColor(textColor);
         mEmptyStateSubtitle.setTextColor(textColor);
+        TypefaceUtils.setTypeface(mEmptyStateTitle,
+                TypefaceUtils.FontFamily.GSF_BODY_MEDIUM_EMPHASIZED);
+        TypefaceUtils.setTypeface(mEmptyStateSubtitle, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM);
+
     }
 
     public void updateFontSize() {
@@ -322,6 +327,7 @@
 
         TextView viewName = overflowView.findViewById(R.id.bubble_view_name);
         viewName.setTextColor(textColor);
+        TypefaceUtils.setTypeface(viewName, TypefaceUtils.FontFamily.GSF_LABEL_LARGE);
 
         return new ViewHolder(overflowView, mPositioner);
     }
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 dd5a23a..3dce456 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
@@ -89,6 +89,8 @@
 import com.android.wm.shell.bubbles.animation.StackAnimationController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.TypefaceUtils;
+import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
 import com.android.wm.shell.shared.animation.Interpolators;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
 import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
@@ -1397,6 +1399,14 @@
         // The menu itself should respect locale direction so the icons are on the correct side.
         mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
         addView(mManageMenu);
+
+        // Doesn't seem to work unless view is added; so set font after.
+        TypefaceUtils.setTypeface(findViewById(R.id.manage_dismiss), FontFamily.GSF_LABEL_LARGE);
+        TypefaceUtils.setTypeface(findViewById(R.id.manage_dont_bubble),
+                FontFamily.GSF_LABEL_LARGE);
+        TypefaceUtils.setTypeface(mManageSettingsText, FontFamily.GSF_LABEL_LARGE);
+        TypefaceUtils.setTypeface(findViewById(R.id.bubble_manage_menu_fullscreen_title),
+                FontFamily.GSF_LABEL_LARGE);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index 39a2a7b..d2ad708 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -27,6 +27,7 @@
 import android.widget.LinearLayout
 import com.android.internal.R.color.system_neutral1_900
 import com.android.wm.shell.R
+import com.android.wm.shell.shared.TypefaceUtils
 import com.android.wm.shell.shared.animation.Interpolators
 
 /**
@@ -53,6 +54,12 @@
 
     init {
         LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this)
+        TypefaceUtils.setTypeface(findViewById(R.id.user_education_title),
+            TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+        TypefaceUtils.setTypeface(findViewById(R.id.user_education_description),
+            TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
+        TypefaceUtils.setTypeface(manageButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE_EMPHASIZED)
+        TypefaceUtils.setTypeface(gotItButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE_EMPHASIZED)
         visibility = View.GONE
         elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 1660619..9ac05989 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -26,6 +26,7 @@
 import android.widget.TextView
 import com.android.internal.util.ContrastColorUtil
 import com.android.wm.shell.R
+import com.android.wm.shell.shared.TypefaceUtils
 import com.android.wm.shell.shared.animation.Interpolators
 
 /**
@@ -59,6 +60,9 @@
 
     init {
         LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this)
+        TypefaceUtils.setTypeface(titleTextView,
+            TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+        TypefaceUtils.setTypeface(descTextView, TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
 
         visibility = View.GONE
         elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 9d4f904..3543556 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -203,7 +203,11 @@
             draggedObject: MagnetizedObject<*>,
         ) {
             dragListener.onReleased(inDismiss = true)
-            pinController.onDragEnd()
+            if (dropTargetManager != null) {
+                dropTargetManager.onDragEnded()
+            } else {
+                pinController.onDragEnd()
+            }
             dismissView.hide()
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 3997412..2cc9387 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -147,15 +147,23 @@
                                 Log.w(TAG, "dropped invalid bubble: " + mExpandedBubble);
                                 return;
                             }
+
+                            final boolean isBubbleLeft = zone instanceof DragZone.Bubble.Left;
+                            final boolean isBubbleRight = zone instanceof DragZone.Bubble.Right;
+                            if (!isBubbleLeft && !isBubbleRight) {
+                                // If we didn't finish the "change" animation make sure to animate
+                                // it back to the right spot
+                                locationChangeListener.onChange(mInitialLocation);
+                            }
                             if (zone instanceof DragZone.FullScreen) {
                                 ((Bubble) mExpandedBubble).getTaskView().moveToFullscreen();
                                 // Make sure location change listener is updated with the initial
                                 // location -- even if we "switched sides" during the drag, since
                                 // we've ended up in fullscreen, the location shouldn't change.
                                 locationChangeListener.onRelease(mInitialLocation);
-                            } else if (zone instanceof DragZone.Bubble.Left) {
+                            } else if (isBubbleLeft) {
                                 locationChangeListener.onRelease(BubbleBarLocation.LEFT);
-                            } else if (zone instanceof DragZone.Bubble.Right) {
+                            } else if (isBubbleRight) {
                                 locationChangeListener.onRelease(BubbleBarLocation.RIGHT);
                             }
                         }
@@ -189,7 +197,7 @@
                         @NonNull
                         @Override
                         public SplitScreenMode getSplitScreenMode() {
-                            return SplitScreenMode.NONE;
+                            return SplitScreenMode.UNSUPPORTED;
                         }
                     },
                     new DragZoneFactory.DesktopWindowModeChecker() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
index 6c14d83..bccc6dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
@@ -25,6 +25,7 @@
 import android.widget.TextView;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.shared.TypefaceUtils;
 
 /**
  * Bubble bar expanded view menu item view to display menu action details
@@ -55,6 +56,7 @@
         super.onFinishInflate();
         mImageView = findViewById(R.id.bubble_bar_menu_item_icon);
         mTextView = findViewById(R.id.bubble_bar_menu_item_title);
+        TypefaceUtils.setTypeface(mTextView, TypefaceUtils.FontFamily.GSF_TITLE_MEDIUM);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
index dfbf655..7c0f8e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -33,6 +33,7 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
+import com.android.wm.shell.shared.TypefaceUtils;
 
 import java.util.ArrayList;
 
@@ -75,6 +76,7 @@
         mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section);
         mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon);
         mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
+        TypefaceUtils.setTypeface(mBubbleTitleView, TypefaceUtils.FontFamily.GSF_TITLE_MEDIUM);
         mBubbleDismissIconView = findViewById(R.id.bubble_bar_manage_menu_dismiss_icon);
         updateThemeColors();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
index 7adec39..0bd3a54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -35,6 +35,7 @@
 import com.android.wm.shell.bubbles.BubbleEducationController
 import com.android.wm.shell.bubbles.BubbleViewProvider
 import com.android.wm.shell.bubbles.setup
+import com.android.wm.shell.shared.TypefaceUtils
 import com.android.wm.shell.shared.animation.PhysicsAnimator
 import com.android.wm.shell.shared.bubbles.BubblePopupDrawable
 import com.android.wm.shell.shared.bubbles.BubblePopupView
@@ -108,6 +109,10 @@
         root.getBoundsOnScreen(rootBounds)
         educationView =
             createEducationView(R.layout.bubble_bar_stack_education, root).apply {
+                TypefaceUtils.setTypeface(findViewById(R.id.education_title),
+                    TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+                TypefaceUtils.setTypeface(findViewById(R.id.education_text),
+                    TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
                 setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN)
                 updateEducationPosition(view = this, position, rootBounds)
                 val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f
@@ -153,6 +158,10 @@
 
         educationView =
             createEducationView(R.layout.bubble_bar_manage_education, root).apply {
+                TypefaceUtils.setTypeface(findViewById(R.id.education_manage_title),
+                    TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
+                TypefaceUtils.setTypeface(findViewById(R.id.education_manage_text),
+                    TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
                 pivotY = 0f
                 doOnLayout { it.pivotX = it.width / 2f }
                 setOnClickListener { hideEducation(animated = true) }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index dd5827a..320de2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -142,8 +142,8 @@
 ## Tracing activity starts & finishes in the app process
 
 It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
-(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
-get this trace:
+or via a `WindowContainerTransaction` (ie. if you are repro'ing a bug related to activity starts).
+You can enable this system property to get this trace:
 ```shell
 # Enabling
 adb shell setprop persist.wm.debug.start_activity true
@@ -168,6 +168,21 @@
 adb reboot
 ```
 
+## Tracing transition requests in the Shell
+
+To trace where a new WM transition is started in the Shell, you can enable this system property:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.start_shell_transition true
+adb reboot
+adb logcat -s "ShellTransitions"
+
+# Disabling
+adb shell setprop persist.wm.debug.start_shell_transition \"\"
+adb reboot
+```
+
+
 ## Dumps
 
 Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index cef18f5..c58bb6e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -40,7 +40,6 @@
 import android.view.WindowManager;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
-import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
@@ -341,23 +340,6 @@
         return false;
     }
 
-    /**
-     * @return a change representing a config-at-end activity for a given parent.
-     */
-    @Nullable
-    public TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info,
-            @android.annotation.NonNull WindowContainerToken parent) {
-        for (TransitionInfo.Change change : info.getChanges()) {
-            if (change.getTaskInfo() == null
-                    && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END)
-                    && change.getParent() != null && change.getParent().equals(parent)) {
-                return change;
-            }
-        }
-        return null;
-    }
-
-
     /** Whether a particular package is same as current pip package. */
     public boolean isPackageActiveInPip(@Nullable String packageName) {
         // No-op, to be handled differently in PIP1 and PIP2
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
index 880e143..92f36d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -43,6 +43,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.StringJoiner;
 
 /**
  * A Task Listener implementation used only for CUJs and trigger paths that cannot be initiated via
@@ -114,6 +115,17 @@
         // Set the new params but make sure mPictureInPictureParams is not null.
         mPictureInPictureParams = params == null
                 ? new PictureInPictureParams.Builder().build() : params;
+        logRemoteActions(mPictureInPictureParams);
+    }
+
+    private void logRemoteActions(@android.annotation.NonNull PictureInPictureParams params) {
+        StringJoiner sj = new StringJoiner("|", "[", "]");
+        if (params.hasSetActions()) {
+            params.getActions().forEach((action) -> sj.add(action.getTitle()));
+        }
+
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "PIP remote actions=%s", sj.toString());
     }
 
     /** Add a PipParamsChangedCallback listener. */
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 cfcd563..5d8d8b6 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
@@ -76,6 +76,7 @@
 import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
 import com.android.wm.shell.pip2.animation.PipEnterAnimator;
 import com.android.wm.shell.pip2.phone.transition.PipExpandHandler;
+import com.android.wm.shell.pip2.phone.transition.PipTransitionUtils;
 import com.android.wm.shell.shared.TransitionUtil;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -387,8 +388,8 @@
         mFinishCallback = finishCallback;
         // We expect the PiP activity as a separate change in a config-at-end transition;
         // only flings are not using config-at-end for resize bounds changes
-        TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info,
-                pipChange.getTaskInfo().getToken());
+        TransitionInfo.Change pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange(
+                info, pipChange.getTaskInfo().getToken());
         if (pipActivityChange != null) {
             // Transform calculations use PiP params by default, so make sure they are null to
             // default to using bounds for scaling calculations instead.
@@ -427,8 +428,8 @@
         }
 
         // We expect the PiP activity as a separate change in a config-at-end transition.
-        TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info,
-                pipChange.getTaskInfo().getToken());
+        TransitionInfo.Change pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange(
+                info, pipChange.getTaskInfo().getToken());
         if (pipActivityChange == null) {
             return false;
         }
@@ -497,8 +498,8 @@
         }
 
         // We expect the PiP activity as a separate change in a config-at-end transition.
-        TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info,
-                pipChange.getTaskInfo().getToken());
+        TransitionInfo.Change pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange(
+                info, pipChange.getTaskInfo().getToken());
         if (pipActivityChange == null) {
             return false;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java
index 01cda6c..e562f33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipTransitionUtils.java
@@ -67,6 +67,36 @@
     }
 
     /**
+     * @return a change representing a config-at-end activity for ancestor.
+     */
+    @Nullable
+    public static TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info,
+            @NonNull WindowContainerToken ancestor) {
+        final TransitionInfo.Change ancestorChange =
+                PipTransitionUtils.getChangeByToken(info, ancestor);
+        if (ancestorChange == null) return null;
+
+        // Iterate through changes bottom-to-top, going up the parent chain starting with ancestor.
+        TransitionInfo.Change lastPipChildChange = ancestorChange;
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            if (change == ancestorChange) continue;
+
+            if (change.getParent() != null
+                    && change.getParent().equals(lastPipChildChange.getContainer())) {
+                // Found a child of the last cached child along the ancestral chain.
+                lastPipChildChange = change;
+                if (change.getTaskInfo() == null
+                        && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END)) {
+                    // If this is a config-at-end activity change, then we found the chain leaf.
+                    return change;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
      * @return the leash to interact with the container this change represents.
      * @throws NullPointerException if the leash is null.
      */
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 bb5b5ce..382fa96 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
@@ -536,17 +536,20 @@
     }
 
     /**
-     * Represents a desk whose ID is `mDeskId` and contains the tasks in `mDeskTasks`. Some of these
-     * tasks are minimized and their IDs are contained in the `mMinimizedDeskTasks` set.
+     * Represents a desk whose ID is `mDeskId` inside the display with `mDisplayId` and contains
+     * the tasks in `mDeskTasks`. Some of these tasks are minimized and their IDs are contained
+     * in the `mMinimizedDeskTasks` set.
      */
     private static class Desk {
         final int mDeskId;
+        final int mDisplayId;
         boolean mHasVisibleTasks = false;
         final ArrayList<TaskInfo> mDeskTasks = new ArrayList<>();
         final Set<Integer> mMinimizedDeskTasks = new HashSet<>();
 
-        Desk(int deskId) {
+        Desk(int deskId, int displayId) {
             mDeskId = deskId;
+            mDisplayId = displayId;
         }
 
         void addTask(TaskInfo taskInfo, boolean isMinimized, boolean isVisible) {
@@ -562,7 +565,8 @@
         }
 
         GroupedTaskInfo createDeskTaskInfo() {
-            return GroupedTaskInfo.forDeskTasks(mDeskId, mDeskTasks, mMinimizedDeskTasks);
+            return GroupedTaskInfo.forDeskTasks(mDeskId, mDisplayId, mDeskTasks,
+                    mMinimizedDeskTasks);
         }
     }
 
@@ -601,7 +605,8 @@
     private Desk getOrCreateDesk(int deskId) {
         var desk = mTmpDesks.get(deskId);
         if (desk == null) {
-            desk = new Desk(deskId);
+            desk = new Desk(deskId,
+                    mDesktopUserRepositories.get().getCurrent().getDisplayForDesk(deskId));
             mTmpDesks.put(deskId, desk);
         }
         return desk;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index 1853ffa..320a63a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -34,6 +34,7 @@
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
 import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.phone.transition.PipTransitionUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.StageCoordinator;
 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
@@ -132,7 +133,7 @@
 
         TransitionInfo.Change pipActivityChange = null;
         if (pipChange != null) {
-            pipActivityChange = mPipHandler.getDeferConfigActivityChange(
+            pipActivityChange = PipTransitionUtils.getDeferConfigActivityChange(
                     info, pipChange.getContainer());
             everythingElse.getChanges().remove(pipActivityChange);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 23dfb41..cca9821 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -153,7 +153,6 @@
             return;
         }
         mPendingStartDragTransition = null;
-        if (aborted) return;
 
         if (mPendingHomeVisibilityUpdate != null) {
             notifyHomeVisibilityChanged(mPendingHomeVisibilityUpdate);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index e28a7fa..003ef1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -39,6 +39,7 @@
 
 import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived;
 import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
 import static com.android.wm.shell.shared.TransitionUtil.FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY;
 import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
@@ -52,6 +53,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -138,6 +140,10 @@
         ShellCommandHandler.ShellCommandActionHandler {
     static final String TAG = "ShellTransitions";
 
+    // If set, will print the stack trace for transition starts within the process
+    static final boolean DEBUG_START_TRANSITION = Build.IS_DEBUGGABLE &&
+            SystemProperties.getBoolean("persist.wm.debug.start_shell_transition", false);
+
     /** Set to {@code true} to enable shell transitions. */
     public static final boolean ENABLE_SHELL_TRANSITIONS = getShellTransitEnabled();
     public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
@@ -346,10 +352,10 @@
         mShellController = shellController;
         // The very last handler (0 in the list) should be the default one.
         mHandlers.add(mDefaultTransitionHandler);
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: Default");
         // Next lowest priority is remote transitions.
         mHandlers.add(mRemoteTransitionHandler);
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: Remote");
         shellInit.addInitCallback(this::onInit, this);
         mHomeTransitionObserver = homeTransitionObserver;
         mFocusTransitionObserver = focusTransitionObserver;
@@ -439,7 +445,7 @@
         mHandlers.add(handler);
         // Set initial scale settings.
         handler.setAnimScaleSetting(mTransitionAnimationScaleSetting);
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: %s",
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "addHandler: %s",
                 handler.getClass().getSimpleName());
     }
 
@@ -691,7 +697,7 @@
     void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
         info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
                 info.getDebugId(), transitionToken, info.toString("    " /* prefix */));
         int activeIdx = findByToken(mPendingTransitions, transitionToken);
         if (activeIdx < 0) {
@@ -753,7 +759,7 @@
                 if (tr.isIdle()) continue;
                 hadPreceding = true;
                 // Sleep starts a process of forcing all prior transitions to finish immediately
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                ProtoLog.v(WM_SHELL_TRANSITIONS,
                         "Start finish-for-sync track %d", i);
                 finishForSync(active.mToken, i, null /* forceFinish */);
             }
@@ -797,7 +803,7 @@
         if (info.getRootCount() == 0 && !KeyguardTransitionHandler.handles(info)) {
             // No root-leashes implies that the transition is empty/no-op, so just do
             // housekeeping and return.
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so"
+            ProtoLog.v(WM_SHELL_TRANSITIONS, "No transition roots in %s so"
                     + " abort", active);
             onAbort(active);
             return true;
@@ -839,7 +845,7 @@
                 && allOccluded)) {
             // Treat this as an abort since we are bypassing any merge logic and effectively
             // finishing immediately.
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+            ProtoLog.v(WM_SHELL_TRANSITIONS,
                     "Non-visible anim so abort: %s", active);
             onAbort(active);
             return true;
@@ -873,7 +879,7 @@
     void processReadyQueue(Track track) {
         if (track.mReadyTransitions.isEmpty()) {
             if (track.mActiveTransition == null) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Track %d became idle",
+                ProtoLog.v(WM_SHELL_TRANSITIONS, "Track %d became idle",
                         mTracks.indexOf(track));
                 if (areTracksIdle()) {
                     if (!mReadyDuringSync.isEmpty()) {
@@ -885,7 +891,7 @@
                             if (!success) break;
                         }
                     } else if (mPendingTransitions.isEmpty()) {
-                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition "
+                        ProtoLog.v(WM_SHELL_TRANSITIONS, "All active transition "
                                 + "animations finished");
                         mKnownTransitions.clear();
                         // Run all runnables from the run-when-idle queue.
@@ -926,7 +932,7 @@
             onMerged(playingToken, readyToken);
             return;
         }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition %s ready while"
                 + " %s is still animating. Notify the animating transition"
                 + " in case they can be merged", ready, playing);
         mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
@@ -955,7 +961,7 @@
         }
 
         final Track track = mTracks.get(playing.getTrack());
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
                 merged, playing);
         int readyIdx = 0;
         if (track.mReadyTransitions.isEmpty() || track.mReadyTransitions.get(0) != merged) {
@@ -996,7 +1002,7 @@
     }
 
     private void playTransition(@NonNull ActiveTransition active) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
         final var token = active.mToken;
 
         for (int i = 0; i < mObservers.size(); ++i) {
@@ -1007,12 +1013,12 @@
 
         // If a handler already chose to run this animation, try delegating to it first.
         if (active.mHandler != null) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
+            ProtoLog.v(WM_SHELL_TRANSITIONS, " try firstHandler %s",
                     active.mHandler);
             boolean consumed = active.mHandler.startAnimation(token, active.mInfo,
                     active.mStartT, active.mFinishT, (wct) -> onFinish(token, wct));
             if (consumed) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
+                ProtoLog.v(WM_SHELL_TRANSITIONS, " animated by firstHandler");
                 mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
                     Trace.instant(TRACE_TAG_WINDOW_MANAGER,
@@ -1042,14 +1048,14 @@
     ) {
         for (int i = mHandlers.size() - 1; i >= 0; --i) {
             if (mHandlers.get(i) == skip) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " skip handler %s",
+                ProtoLog.v(WM_SHELL_TRANSITIONS, " skip handler %s",
                         mHandlers.get(i));
                 continue;
             }
             boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT,
                     finishCB);
             if (consumed) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
+                ProtoLog.v(WM_SHELL_TRANSITIONS, " animated by %s",
                         mHandlers.get(i));
                 mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
@@ -1155,7 +1161,7 @@
         for (int i = 0; i < mObservers.size(); ++i) {
             mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
         }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition animation finished "
                 + "(aborted=%b), notifying core %s", active.mAborted, active);
         if (active.mStartT != null) {
             // Applied by now, so clear immediately to remove any references. Do not set to null
@@ -1209,7 +1215,7 @@
 
     void requestStartTransition(@NonNull IBinder transitionToken,
             @Nullable TransitionRequestInfo request) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s",
                 request.getDebugId(), transitionToken, request);
         if (mKnownTransitions.containsKey(transitionToken)) {
             throw new RuntimeException("Transition already started " + transitionToken);
@@ -1228,6 +1234,8 @@
             if (requestResult != null) {
                 active.mHandler = requestResult.first;
                 wct = requestResult.second;
+                ProtoLog.v(WM_SHELL_TRANSITIONS, "Transition (#%d): request handled by %s",
+                        request.getDebugId(), active.mHandler.getClass().getSimpleName());
             }
             if (request.getDisplayChange() != null) {
                 TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
@@ -1273,8 +1281,12 @@
      */
     public IBinder startTransition(@WindowManager.TransitionType int type,
             @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
+        ProtoLog.v(WM_SHELL_TRANSITIONS, "Directly starting a new transition "
                 + "type=%s wct=%s handler=%s", transitTypeToString(type), wct, handler);
+        if (DEBUG_START_TRANSITION) {
+            Log.d(TAG, "startTransition: type=" + transitTypeToString(type)
+                    + " wct=" + wct + " handler=" + handler.getClass().getName(), new Throwable());
+        }
         final ActiveTransition active =
                 new ActiveTransition(mOrganizer.startNewTransition(type, wct));
         active.mHandler = handler;
@@ -1362,7 +1374,7 @@
             }
             // Attempt to merge a SLEEP info to signal that the playing transition needs to
             // fast-forward.
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
+            ProtoLog.v(WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
                     + " into %s via a SLEEP proxy", nextSync, playing);
             playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT, dummyT,
                     playing.mToken, (wct) -> {});
@@ -1598,7 +1610,7 @@
         public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
                 SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)
                 throws RemoteException {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)",
+            ProtoLog.v(WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)",
                     t.getId());
             mMainExecutor.execute(() -> Transitions.this.onTransitionReady(
                     iBinder, transitionInfo, t, finishT));
@@ -1784,8 +1796,9 @@
         pw.println(prefix + TAG);
 
         final String innerPrefix = prefix + "  ";
-        pw.println(prefix + "Handlers:");
-        for (TransitionHandler handler : mHandlers) {
+        pw.println(prefix + "Handlers (ordered by priority):");
+        for (int i = mHandlers.size() - 1; i >= 0; i--) {
+            final TransitionHandler handler = mHandlers.get(i);
             pw.print(innerPrefix);
             pw.print(handler.getClass().getSimpleName());
             pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 5e8c1fe..e08ef58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -49,6 +49,7 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
+import android.window.DesktopExperienceFlags;
 import android.window.DesktopModeFlags;
 import android.window.WindowContainerTransaction;
 
@@ -218,11 +219,17 @@
         relayoutParams.mRunningTaskInfo = taskInfo;
         relayoutParams.mLayoutResId = R.layout.caption_window_decor;
         relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
-        relayoutParams.mShadowRadius = hasGlobalFocus
-                ? context.getResources().getDimensionPixelSize(
-                        R.dimen.freeform_decor_shadow_focused_thickness)
-                : context.getResources().getDimensionPixelSize(
-                        R.dimen.freeform_decor_shadow_unfocused_thickness);
+        if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
+            relayoutParams.mShadowRadiusId = hasGlobalFocus
+                    ? R.dimen.freeform_decor_shadow_focused_thickness
+                    : R.dimen.freeform_decor_shadow_unfocused_thickness;
+        } else {
+            relayoutParams.mShadowRadius = hasGlobalFocus
+                    ? context.getResources().getDimensionPixelSize(
+                    R.dimen.freeform_decor_shadow_focused_thickness)
+                    : context.getResources().getDimensionPixelSize(
+                            R.dimen.freeform_decor_shadow_unfocused_thickness);
+        }
         relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
         relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
         relayoutParams.mIsCaptionVisible = taskInfo.isFreeform()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index bcf9396..e8019e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -72,6 +72,7 @@
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.widget.ImageButton;
+import android.window.DesktopExperienceFlags;
 import android.window.DesktopModeFlags;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerTransaction;
@@ -719,7 +720,8 @@
                 .getScaledTouchSlop();
         final Resources res = mResult.mRootView.getResources();
         final DragResizeWindowGeometry newGeometry = new DragResizeWindowGeometry(
-                mRelayoutParams.mCornerRadius,
+                DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()
+                        ? mResult.mCornerRadius : mRelayoutParams.mCornerRadius,
                 new Size(mResult.mWidth, mResult.mHeight),
                 getResizeEdgeHandleSize(res), getResizeHandleEdgeInset(res),
                 getFineResizeCornerSize(res), getLargeResizeCornerSize(res),
@@ -1072,13 +1074,23 @@
         }
         if (isAppHeader
                 && DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ hasGlobalFocus)) {
-            relayoutParams.mShadowRadius = hasGlobalFocus
-                    ? context.getResources().getDimensionPixelSize(
-                            R.dimen.freeform_decor_shadow_focused_thickness)
-                    : context.getResources().getDimensionPixelSize(
-                            R.dimen.freeform_decor_shadow_unfocused_thickness);
+            if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
+                relayoutParams.mShadowRadiusId = hasGlobalFocus
+                        ? R.dimen.freeform_decor_shadow_focused_thickness
+                        : R.dimen.freeform_decor_shadow_unfocused_thickness;
+            } else {
+                relayoutParams.mShadowRadius = hasGlobalFocus
+                        ? context.getResources().getDimensionPixelSize(
+                        R.dimen.freeform_decor_shadow_focused_thickness)
+                        : context.getResources().getDimensionPixelSize(
+                                R.dimen.freeform_decor_shadow_unfocused_thickness);
+            }
         } else {
-            relayoutParams.mShadowRadius = INVALID_SHADOW_RADIUS;
+            if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
+                relayoutParams.mShadowRadiusId = Resources.ID_NULL;
+            } else {
+                relayoutParams.mShadowRadius = INVALID_SHADOW_RADIUS;
+            }
         }
         relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
         relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
@@ -1104,8 +1116,13 @@
         relayoutParams.mWindowDecorConfig = windowDecorConfig;
 
         if (DesktopModeStatus.useRoundedCorners()) {
-            relayoutParams.mCornerRadius = shouldIgnoreCornerRadius ? INVALID_CORNER_RADIUS :
-                    getCornerRadius(context, relayoutParams.mLayoutResId);
+            if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
+                relayoutParams.mCornerRadiusId = shouldIgnoreCornerRadius ? Resources.ID_NULL :
+                        getCornerRadiusId(relayoutParams.mLayoutResId);
+            } else {
+                relayoutParams.mCornerRadius = shouldIgnoreCornerRadius ? INVALID_CORNER_RADIUS :
+                        getCornerRadius(context, relayoutParams.mLayoutResId);
+            }
         }
         // Set opaque background for all freeform tasks to prevent freeform tasks below
         // from being visible if freeform task window above is translucent.
@@ -1113,6 +1130,7 @@
         relayoutParams.mShouldSetBackground = DesktopModeStatus.shouldSetBackground(taskInfo);
     }
 
+    @Deprecated
     private static int getCornerRadius(@NonNull Context context, int layoutResId) {
         if (layoutResId == R.layout.desktop_mode_app_header) {
             return loadDimensionPixelSize(context.getResources(),
@@ -1122,6 +1140,14 @@
         return INVALID_CORNER_RADIUS;
     }
 
+    private static int getCornerRadiusId(int layoutResId) {
+        if (layoutResId == R.layout.desktop_mode_app_header) {
+            return com.android.wm.shell.shared.R.dimen
+                    .desktop_windowing_freeform_rounded_corner_radius;
+        }
+        return Resources.ID_NULL;
+    }
+
     /**
      * If task has focused window decor, return the caption id of the fullscreen caption size
      * resource. Otherwise, return ID_NULL and caption width be set to task width.
@@ -1756,8 +1782,10 @@
         mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
         disposeResizeVeil();
         disposeStatusBarInputLayer();
-        mWindowDecorViewHolder.close();
-        mWindowDecorViewHolder = null;
+        if (mWindowDecorViewHolder != null) {
+            mWindowDecorViewHolder.close();
+            mWindowDecorViewHolder = null;
+        }
         if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
             notifyNoCaptionHandle();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
index eb324f7..2382427 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -278,13 +278,16 @@
                         currentDisplayLayout,
                     )
                 )
-
-                multiDisplayDragMoveIndicatorController.onDragEnd(
-                    desktopWindowDecoration.mTaskInfo.taskId,
-                    transactionSupplier,
-                )
             }
 
+            // Call the MultiDisplayDragMoveIndicatorController to clear any active indicator
+            // surfaces. This is necessary even if the drag ended on the same display, as surfaces
+            // may have been created for other displays during the drag.
+            multiDisplayDragMoveIndicatorController.onDragEnd(
+                desktopWindowDecoration.mTaskInfo.taskId,
+                transactionSupplier,
+            )
+
             interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
         }
 
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 91a899c..6fd963f 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
@@ -47,6 +47,7 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowlessWindowManager;
+import android.window.DesktopExperienceFlags;
 import android.window.SurfaceSyncGroup;
 import android.window.TaskConstants;
 import android.window.WindowContainerToken;
@@ -286,6 +287,14 @@
         outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
         outResult.mCaptionY = 0;
         outResult.mCaptionTopPadding = params.mCaptionTopPadding;
+        if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
+            outResult.mCornerRadius = params.mCornerRadiusId == Resources.ID_NULL
+                    ? INVALID_CORNER_RADIUS : loadDimensionPixelSize(resources,
+                    params.mCornerRadiusId);
+            outResult.mShadowRadius = params.mShadowRadiusId == Resources.ID_NULL
+                    ? INVALID_SHADOW_RADIUS : loadDimensionPixelSize(resources,
+                    params.mShadowRadiusId);
+        }
 
         Trace.beginSection("relayout-createViewHostIfNeeded");
         createViewHostIfNeeded(mDecorWindowContext, mDisplay);
@@ -497,9 +506,16 @@
                     .setPosition(mTaskSurface, taskPosition.x, taskPosition.y);
         }
 
-        if (params.mShadowRadius != INVALID_SHADOW_RADIUS) {
-            startT.setShadowRadius(mTaskSurface, params.mShadowRadius);
-            finishT.setShadowRadius(mTaskSurface, params.mShadowRadius);
+        if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
+            if (outResult.mShadowRadius != INVALID_SHADOW_RADIUS) {
+                startT.setShadowRadius(mTaskSurface, outResult.mShadowRadius);
+                finishT.setShadowRadius(mTaskSurface, outResult.mShadowRadius);
+            }
+        } else {
+            if (params.mShadowRadius != INVALID_SHADOW_RADIUS) {
+                startT.setShadowRadius(mTaskSurface, params.mShadowRadius);
+                finishT.setShadowRadius(mTaskSurface, params.mShadowRadius);
+            }
         }
 
         if (params.mSetTaskVisibilityPositionAndCrop) {
@@ -517,9 +533,16 @@
             startT.unsetColor(mTaskSurface);
         }
 
-        if (params.mCornerRadius != INVALID_CORNER_RADIUS) {
-            startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
-            finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+        if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
+            if (outResult.mCornerRadius != INVALID_CORNER_RADIUS) {
+                startT.setCornerRadius(mTaskSurface, outResult.mCornerRadius);
+                finishT.setCornerRadius(mTaskSurface, outResult.mCornerRadius);
+            }
+        } else {
+            if (params.mCornerRadius != INVALID_CORNER_RADIUS) {
+                startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+                finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+            }
         }
     }
 
@@ -824,9 +847,14 @@
         @InsetsSource.Flags int mInsetSourceFlags;
         final Region mDisplayExclusionRegion = Region.obtain();
 
+        @Deprecated
         int mShadowRadius = INVALID_SHADOW_RADIUS;
+        @Deprecated
         int mCornerRadius = INVALID_CORNER_RADIUS;
 
+        int mShadowRadiusId = Resources.ID_NULL;
+        int mCornerRadiusId = Resources.ID_NULL;
+
         int mCaptionTopPadding;
         boolean mIsCaptionVisible;
 
@@ -849,9 +877,13 @@
             mIsInsetSource = true;
             mInsetSourceFlags = 0;
             mDisplayExclusionRegion.setEmpty();
-
-            mShadowRadius = INVALID_SHADOW_RADIUS;
-            mCornerRadius = INVALID_SHADOW_RADIUS;
+            if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
+                mShadowRadiusId = Resources.ID_NULL;
+                mCornerRadiusId = Resources.ID_NULL;
+            } else {
+                mShadowRadius = INVALID_SHADOW_RADIUS;
+                mCornerRadius = INVALID_SHADOW_RADIUS;
+            }
 
             mCaptionTopPadding = 0;
             mIsCaptionVisible = false;
@@ -893,6 +925,8 @@
         int mWidth;
         int mHeight;
         T mRootView;
+        int mCornerRadius;
+        int mShadowRadius;
 
         void reset() {
             mWidth = 0;
@@ -904,6 +938,10 @@
             mCaptionTopPadding = 0;
             mCustomizableCaptionRegion.setEmpty();
             mRootView = null;
+            if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
+                mCornerRadius = INVALID_CORNER_RADIUS;
+                mShadowRadius = INVALID_SHADOW_RADIUS;
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
index 509f4f2..8e1cf167 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
@@ -254,6 +254,16 @@
     }
 }
 
+/**
+ * Checks that surfaces are still within the expected region after snapping to a snap point.
+ *
+ * @param component The component we are checking (should be one of the two split apps)
+ * @param landscapePosLeft If [true], and device is in left/right split, app is on the left side of
+ * the screen. Has no meaning if device is in top/bottom split.
+ * @param portraitPosTop If [true], and device is in top/bottom split, app is on the top side of
+ * the screen. Has no meaning if device is in left/right split.
+ * @param rotation The rotation state of the display.
+ */
 fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
     component: IComponentMatcher,
     landscapePosLeft: Boolean,
@@ -268,10 +278,12 @@
         visibleRegion(component).isNotEmpty()
         visibleRegion(component)
             .coversAtMost(
+                // TODO (b/403082705): Should use the new method for determining left/right split.
                 if (displayBounds.width() > displayBounds.height()) {
                     if (landscapePosLeft) {
                         Region(
-                            0,
+                            // TODO (b/403304310): Check if we're in an offscreen-enabled mode.
+                            -displayBounds.right, // the receding app can go offscreen
                             0,
                             (dividerRegion.left + dividerRegion.right) / 2,
                             displayBounds.bottom
@@ -280,7 +292,7 @@
                         Region(
                             (dividerRegion.left + dividerRegion.right) / 2,
                             0,
-                            displayBounds.right,
+                            displayBounds.right * 2, // the receding app can go offscreen
                             displayBounds.bottom
                         )
                     }
@@ -288,7 +300,7 @@
                     if (portraitPosTop) {
                         Region(
                             0,
-                            0,
+                            -displayBounds.bottom, // the receding app can go offscreen
                             displayBounds.right,
                             (dividerRegion.top + dividerRegion.bottom) / 2
                         )
@@ -297,7 +309,7 @@
                             0,
                             (dividerRegion.top + dividerRegion.bottom) / 2,
                             displayBounds.right,
-                            displayBounds.bottom
+                            displayBounds.bottom * 2 // the receding app can go offscreen
                         )
                     }
                 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index 49d6877..e4183f1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -310,12 +310,18 @@
         }
     }
 
+    /**
+     * Drags the divider, then releases, making it snap to a new snap point.
+     */
     fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+        // Find the first display that is turned on (making the assumption that there is only one).
         val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual && it.isOn }
+                ?.layerStackSpace ?: error("Display not found")
         val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        dividerBar.drag(Point(displayBounds.width() * 1 / 3, displayBounds.height() * 2 / 3), 200)
+        // Drag to a point on the lower left of the screen -- this will cause the divider to snap
+        // to the left- or bottom-side snap point, shrinking the "primary" test app.
+        dividerBar.drag(Point(displayBounds.width() * 1 / 4, displayBounds.height() * 3 / 4), 200)
 
         wmHelper
             .StateSyncBuilder()
diff --git a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
index aa1b241..33ea0ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
@@ -18,6 +18,8 @@
     <!-- Resources used in WindowDecorationTests -->
     <dimen name="test_freeform_decor_caption_height">32dp</dimen>
     <dimen name="test_freeform_decor_caption_menu_width">216dp</dimen>
+    <dimen name="test_freeform_shadow_radius">20dp</dimen>
+    <dimen name="test_freeform_corner_radius">16dp</dimen>
     <dimen name="test_window_decor_left_outset">10dp</dimen>
     <dimen name="test_window_decor_top_outset">20dp</dimen>
     <dimen name="test_window_decor_right_outset">30dp</dimen>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
index 75f6bda..4e8812d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
@@ -21,6 +21,7 @@
 import android.graphics.Rect
 import android.os.Parcel
 import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
 import android.window.IWindowContainerToken
 import android.window.WindowContainerToken
 import androidx.test.filters.SmallTest
@@ -281,7 +282,8 @@
         val task2 = createTaskInfo(id = 2)
 
         val taskInfo = GroupedTaskInfo.forDeskTasks(
-            /* deskId = */ 500, listOf(task1, task2), setOf())
+            /* deskId = */ 500, DEFAULT_DISPLAY, listOf(task1, task2), setOf()
+        )
 
         assertThat(taskInfo.deskId).isEqualTo(500)
         assertThat(taskInfo.getTaskById(1)).isEqualTo(task1)
@@ -335,6 +337,7 @@
     ): GroupedTaskInfo {
         return GroupedTaskInfo.forDeskTasks(
             deskId,
+            DEFAULT_DISPLAY,
             freeformTaskIds.map { createTaskInfo(it) }.toList(),
             minimizedTaskIds.toSet())
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index a122c38..55bff09 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -222,7 +222,7 @@
 
     @Test
     @EnableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX})
-    public void startDragToDesktopAborted_doesNotTriggerCallback() throws RemoteException {
+    public void startDragToDesktopAborted_triggersCallback() throws RemoteException {
         TransitionInfo info = mock(TransitionInfo.class);
         TransitionInfo.Change change = mock(TransitionInfo.Change.class);
         ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
@@ -239,7 +239,7 @@
 
         mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ true);
 
-        verify(mListener, never()).onHomeVisibilityChanged(/* isVisible= */ anyBoolean());
+        verify(mListener).onHomeVisibilityChanged(/* isVisible= */ true);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index f37f2fb..f7b9c335 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -59,6 +59,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -341,7 +342,8 @@
     }
 
     @Test
-    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreSetForFreeform() {
+    @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreSetForFreeform_dynamicDisabled() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
         RelayoutParams relayoutParams = new RelayoutParams();
@@ -353,7 +355,8 @@
     }
 
     @Test
-    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForFullscreen() {
+    @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForFullscreen_dynamicDisabled() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         RelayoutParams relayoutParams = new RelayoutParams();
@@ -364,7 +367,8 @@
     }
 
     @Test
-    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForSplit() {
+    @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForSplit_dynamicDisabled() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         RelayoutParams relayoutParams = new RelayoutParams();
@@ -375,7 +379,8 @@
     }
 
     @Test
-    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersSetForFreeform() {
+    @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersSetForFreeform_dynamicDisabled() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
         fillRoundedCornersResources(/* fillValue= */ 30);
@@ -387,7 +392,8 @@
     }
 
     @Test
-    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForFullscreen() {
+    @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForFullscreen_dynamicDisabled() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         fillRoundedCornersResources(/* fillValue= */ 30);
@@ -399,7 +405,8 @@
     }
 
     @Test
-    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForSplit() {
+    @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForSplit_dynamicDisabled() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         fillRoundedCornersResources(/* fillValue= */ 30);
@@ -411,7 +418,8 @@
     }
 
     @Test
-    public void updateRelayoutParams_shouldIgnoreCornerRadius_roundedCornersNotSet() {
+    @DisableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_shouldIgnoreCornerRadius_roundedCornersNotSet_dynamicDisabled() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
         fillRoundedCornersResources(/* fillValue= */ 30);
@@ -440,6 +448,107 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreSetForFreeform() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        updateRelayoutParams(relayoutParams, taskInfo);
+
+        assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForFullscreen() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        updateRelayoutParams(relayoutParams, taskInfo);
+
+        assertThat(relayoutParams.mShadowRadiusId).isEqualTo(Resources.ID_NULL);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForSplit() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        updateRelayoutParams(relayoutParams, taskInfo);
+
+        assertThat(relayoutParams.mShadowRadiusId).isEqualTo(Resources.ID_NULL);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersSetForFreeform() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        updateRelayoutParams(relayoutParams, taskInfo);
+
+        assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForFullscreen() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        updateRelayoutParams(relayoutParams, taskInfo);
+
+        assertThat(relayoutParams.mCornerRadiusId).isEqualTo(Resources.ID_NULL);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForSplit() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        updateRelayoutParams(relayoutParams, taskInfo);
+
+        assertThat(relayoutParams.mCornerRadiusId).isEqualTo(Resources.ID_NULL);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX)
+    public void updateRelayoutParams_shouldIgnoreCornerRadius_roundedCornersNotSet() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                mMockSplitScreenController,
+                DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
+                DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
+                DEFAULT_IS_STATUSBAR_VISIBLE,
+                DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
+                DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
+                DEFAULT_IS_DRAGGING,
+                new InsetsState(),
+                DEFAULT_HAS_GLOBAL_FOCUS,
+                mExclusionRegion,
+                /* shouldIgnoreCornerRadius= */ true,
+                DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS,
+                DEFAULT_IS_RECENTS_TRANSITION_RUNNING,
+                DEFAULT_IS_MOVING_TO_BACK);
+
+        assertThat(relayoutParams.mCornerRadiusId).isEqualTo(Resources.ID_NULL);
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
     public void updateRelayoutParams_appHeader_usesTaskDensity() {
         final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
index 0798613..24a46aa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -63,6 +63,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoInteractions
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
@@ -210,6 +211,7 @@
                 eq(taskPositioner),
             )
         verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+        verifyNoInteractions(mockMultiDisplayDragMoveIndicatorController)
     }
 
     @Test
@@ -248,6 +250,7 @@
 
         verify(mockDesktopWindowDecoration, never()).showResizeVeil(any())
         verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+        verify(mockMultiDisplayDragMoveIndicatorController).onDragEnd(eq(TASK_ID), any())
         Assert.assertEquals(rectAfterEnd, endBounds)
     }
 
@@ -268,6 +271,7 @@
 
         verify(spyDisplayLayout0, never()).localPxToGlobalDp(any(), any())
         verify(spyDisplayLayout0, never()).globalDpToLocalPx(any(), any())
+        verify(mockMultiDisplayDragMoveIndicatorController).onDragEnd(eq(TASK_ID), any())
     }
 
     @Test
@@ -290,6 +294,7 @@
 
         verify(mockDesktopWindowDecoration, never()).showResizeVeil(any())
         verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+        verify(mockMultiDisplayDragMoveIndicatorController).onDragEnd(eq(TASK_ID), any())
         Assert.assertEquals(rectAfterEnd, endBounds)
     }
 
@@ -346,6 +351,7 @@
                 },
                 eq(taskPositioner),
             )
+        verifyNoInteractions(mockMultiDisplayDragMoveIndicatorController)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 2e95a97..c691dc7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -61,7 +61,8 @@
 import android.graphics.Region;
 import android.os.Handler;
 import android.os.LocaleList;
-import android.testing.AndroidTestingRunner;
+import android.platform.test.annotations.UsesFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.util.DisplayMetrics;
 import android.view.AttachedSurfaceControl;
 import android.view.Display;
@@ -78,6 +79,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -96,6 +98,9 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
@@ -108,7 +113,8 @@
  * atest WMShellUnitTests:WindowDecorationTests
  */
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
+@UsesFlags(com.android.window.flags.Flags.class)
 public class WindowDecorationTests extends ShellTestCase {
     private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
     private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
@@ -116,6 +122,12 @@
     private static final int SHADOW_RADIUS = 10;
     private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
 
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return FlagsParameterization.allCombinationsOf(
+                Flags.FLAG_ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX);
+    }
+
     private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
             new WindowDecoration.RelayoutResult<>();
 
@@ -156,6 +168,10 @@
     private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
     private int mCaptionMenuWidthId;
 
+    public WindowDecorationTests(FlagsParameterization flags) {
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     @Before
     public void setUp() {
         mMockSurfaceControlStartT = createMockSurfaceControlTransaction();
@@ -165,8 +181,13 @@
         mRelayoutParams.mLayoutResId = 0;
         mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
         mCaptionMenuWidthId = R.dimen.test_freeform_decor_caption_menu_width;
-        mRelayoutParams.mShadowRadius = SHADOW_RADIUS;
-        mRelayoutParams.mCornerRadius = CORNER_RADIUS;
+        if (Flags.enableDynamicRadiusComputationBugfix()) {
+            mRelayoutParams.mShadowRadiusId = R.dimen.test_freeform_shadow_radius;
+            mRelayoutParams.mCornerRadiusId = R.dimen.test_freeform_corner_radius;
+        } else {
+            mRelayoutParams.mShadowRadius = SHADOW_RADIUS;
+            mRelayoutParams.mCornerRadius = CORNER_RADIUS;
+        }
 
         when(mMockDisplayController.getDisplay(Display.DEFAULT_DISPLAY))
                 .thenReturn(mock(Display.class));
@@ -282,9 +303,21 @@
                 any(),
                 anyInt());
 
-        verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
-        verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
-        verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, SHADOW_RADIUS);
+        if (Flags.enableDynamicRadiusComputationBugfix()) {
+            final int cornerRadius = WindowDecoration.loadDimensionPixelSize(
+                    windowDecor.mDecorWindowContext.getResources(),
+                    mRelayoutParams.mCornerRadiusId);
+            verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, cornerRadius);
+            verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, cornerRadius);
+            final int shadowRadius = WindowDecoration.loadDimensionPixelSize(
+                    windowDecor.mDecorWindowContext.getResources(),
+                    mRelayoutParams.mShadowRadiusId);
+            verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, shadowRadius);
+        } else {
+            verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
+            verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
+            verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, SHADOW_RADIUS);
+        }
 
         assertEquals(300, mRelayoutResult.mWidth);
         assertEquals(100, mRelayoutResult.mHeight);
@@ -1198,7 +1231,8 @@
         }
 
         @Override
-        public void setTaskFocusState(boolean focused) {}
+        public void setTaskFocusState(boolean focused) {
+        }
     }
 
     private class TestWindowDecoration extends WindowDecoration<TestView> {
diff --git a/libs/hostgraphics/include/gui/BufferItemConsumer.h b/libs/hostgraphics/include/gui/BufferItemConsumer.h
index 5c96c82..b9ff0a77 100644
--- a/libs/hostgraphics/include/gui/BufferItemConsumer.h
+++ b/libs/hostgraphics/include/gui/BufferItemConsumer.h
@@ -48,6 +48,10 @@
         return mConsumer->acquireBuffer(item, presentWhen, 0);
     }
 
+    status_t attachBuffer(BufferItem*, const sp<GraphicBuffer>&) {
+        return INVALID_OPERATION;
+    }
+
     status_t releaseBuffer(const BufferItem& item,
                            const sp<Fence>& releaseFence = Fence::NO_FENCE) {
         return OK;
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index a892e88..ab1be7e 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -139,6 +139,7 @@
                 "libandroidfw",
                 "libcrypto",
                 "libsync",
+                "libgui",
                 "libui",
                 "aconfig_text_flags_c_lib",
                 "aconfig_view_accessibility_flags_c_lib",
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index 9d16ee8..7e1eb70 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -16,6 +16,7 @@
 
 #include "WebViewFunctorManager.h"
 
+#include <gui/SurfaceComposerClient.h>
 #include <log/log.h>
 #include <private/hwui/WebViewFunctor.h>
 #include <utils/Trace.h>
@@ -43,7 +44,7 @@
 
     static ASurfaceControl* getSurfaceControl() {
         ALOG_ASSERT(sCurrentFunctor);
-        return sCurrentFunctor->getSurfaceControl();
+        return reinterpret_cast<ASurfaceControl*>(sCurrentFunctor->getSurfaceControl());
     }
     static void mergeTransaction(ASurfaceTransaction* transaction) {
         ALOG_ASSERT(sCurrentFunctor);
@@ -129,12 +130,12 @@
     renderthread::CanvasContext* activeContext = renderthread::CanvasContext::getActiveContext();
     if (!activeContext) return false;
 
-    ASurfaceControl* rootSurfaceControl = activeContext->getSurfaceControl();
+    sp<SurfaceControl> rootSurfaceControl = activeContext->getSurfaceControl();
     if (!rootSurfaceControl) return false;
 
     int32_t rgid = activeContext->getSurfaceControlGenerationId();
     if (mParentSurfaceControlGenerationId != rgid) {
-        reparentSurfaceControl(rootSurfaceControl);
+        reparentSurfaceControl(reinterpret_cast<ASurfaceControl*>(rootSurfaceControl.get()));
         mParentSurfaceControlGenerationId = rgid;
     }
 
@@ -210,33 +211,35 @@
     mCallbacks.removeOverlays(mFunctor, mData, currentFunctor.mergeTransaction);
     if (mSurfaceControl) {
         reparentSurfaceControl(nullptr);
-        auto funcs = renderthread::RenderThread::getInstance().getASurfaceControlFunctions();
-        funcs.releaseFunc(mSurfaceControl);
         mSurfaceControl = nullptr;
     }
 }
 
 ASurfaceControl* WebViewFunctor::getSurfaceControl() {
     ATRACE_NAME("WebViewFunctor::getSurfaceControl");
-    if (mSurfaceControl != nullptr) return mSurfaceControl;
+    if (mSurfaceControl != nullptr) {
+        return reinterpret_cast<ASurfaceControl*>(mSurfaceControl.get());
+    }
 
     renderthread::CanvasContext* activeContext = renderthread::CanvasContext::getActiveContext();
     LOG_ALWAYS_FATAL_IF(activeContext == nullptr, "Null active canvas context!");
 
-    ASurfaceControl* rootSurfaceControl = activeContext->getSurfaceControl();
+    sp<SurfaceControl> rootSurfaceControl = activeContext->getSurfaceControl();
     LOG_ALWAYS_FATAL_IF(rootSurfaceControl == nullptr, "Null root surface control!");
 
-    auto funcs = renderthread::RenderThread::getInstance().getASurfaceControlFunctions();
     mParentSurfaceControlGenerationId = activeContext->getSurfaceControlGenerationId();
-    mSurfaceControl = funcs.createFunc(rootSurfaceControl, "Webview Overlay SurfaceControl");
-    ASurfaceTransaction* transaction = funcs.transactionCreateFunc();
+
+    SurfaceComposerClient* client = rootSurfaceControl->getClient().get();
+    mSurfaceControl = client->createSurface(
+            String8("Webview Overlay SurfaceControl"), 0 /* width */, 0 /* height */,
+            // Format is only relevant for buffer queue layers.
+            PIXEL_FORMAT_UNKNOWN /* format */, ISurfaceComposerClient::eFXSurfaceBufferState,
+            rootSurfaceControl->getHandle());
+
     activeContext->prepareSurfaceControlForWebview();
-    funcs.transactionSetZOrderFunc(transaction, mSurfaceControl, -1);
-    funcs.transactionSetVisibilityFunc(transaction, mSurfaceControl,
-                                       ASURFACE_TRANSACTION_VISIBILITY_SHOW);
-    funcs.transactionApplyFunc(transaction);
-    funcs.transactionDeleteFunc(transaction);
-    return mSurfaceControl;
+    SurfaceComposerClient::Transaction transaction;
+    transaction.setLayer(mSurfaceControl, -1).show(mSurfaceControl).apply();
+    return reinterpret_cast<ASurfaceControl*>(mSurfaceControl.get());
 }
 
 void WebViewFunctor::mergeTransaction(ASurfaceTransaction* transaction) {
@@ -249,8 +252,7 @@
         done = activeContext->mergeTransaction(transaction, mSurfaceControl);
     }
     if (!done) {
-        auto funcs = renderthread::RenderThread::getInstance().getASurfaceControlFunctions();
-        funcs.transactionApplyFunc(transaction);
+        reinterpret_cast<SurfaceComposerClient::Transaction*>(transaction)->apply();
     }
 }
 
@@ -258,11 +260,10 @@
     ATRACE_NAME("WebViewFunctor::reparentSurfaceControl");
     if (mSurfaceControl == nullptr) return;
 
-    auto funcs = renderthread::RenderThread::getInstance().getASurfaceControlFunctions();
-    ASurfaceTransaction* transaction = funcs.transactionCreateFunc();
-    funcs.transactionReparentFunc(transaction, mSurfaceControl, parent);
-    mergeTransaction(transaction);
-    funcs.transactionDeleteFunc(transaction);
+    SurfaceComposerClient::Transaction transaction;
+    transaction.reparent(mSurfaceControl, sp<SurfaceControl>::fromExisting(
+                                                  reinterpret_cast<SurfaceControl*>(parent)));
+    mergeTransaction(reinterpret_cast<ASurfaceTransaction*>(&transaction));
 }
 
 void WebViewFunctor::reportRenderingThreads(const pid_t* thread_ids, size_t size) {
diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h
index ec17640..ac16f91 100644
--- a/libs/hwui/WebViewFunctorManager.h
+++ b/libs/hwui/WebViewFunctorManager.h
@@ -25,7 +25,11 @@
 #include <mutex>
 #include <vector>
 
-namespace android::uirenderer {
+namespace android {
+
+class SurfaceControl;
+
+namespace uirenderer {
 
 class WebViewFunctorManager;
 
@@ -100,7 +104,9 @@
     bool mHasContext = false;
     bool mCreatedHandle = false;
     int32_t mParentSurfaceControlGenerationId = 0;
-    ASurfaceControl* mSurfaceControl = nullptr;
+#ifdef __ANDROID__
+    sp<SurfaceControl> mSurfaceControl = nullptr;
+#endif
     std::vector<pid_t> mRenderingThreads;
 };
 
@@ -126,4 +132,5 @@
     std::vector<sp<WebViewFunctor::Handle>> mActiveFunctors;
 };
 
-}  // namespace android::uirenderer
+}  // namespace uirenderer
+}  // namespace android
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index d3fc91b..b3badd0 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -203,4 +203,11 @@
   description: "Initialize GraphicBufferAllocater on ViewRootImpl init, to avoid blocking on init during buffer allocation, improving app launch latency."
   bug: "389908734"
   is_fixed_read_only: true
+}
+
+flag {
+  name: "bitmap_parcel_ashmem_as_immutable"
+  namespace: "system_performance"
+  description: "Whether to parcel implicit copies of bitmaps to ashmem as immutable"
+  bug: "400807118"
 }
\ No newline at end of file
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 27d4ac7..104ece6 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -28,8 +28,18 @@
 #include "SkRefCnt.h"
 #include "SkStream.h"
 #include "SkTypes.h"
+#include "android/binder_parcel.h"
 #include "android_nio_utils.h"
 
+#ifdef __ANDROID__
+#include <com_android_graphics_hwui_flags.h>
+namespace hwui_flags = com::android::graphics::hwui::flags;
+#else
+namespace hwui_flags {
+constexpr bool bitmap_parcel_ashmem_as_immutable() { return false; }
+}
+#endif
+
 #define DEBUG_PARCEL 0
 
 static jclass   gBitmap_class;
@@ -841,6 +851,23 @@
 #endif
 }
 
+// Returns whether this bitmap should be written to the parcel as mutable.
+static bool shouldParcelAsMutable(SkBitmap& bitmap, AParcel* parcel) {
+    // If the bitmap is immutable, then parcel as immutable.
+    if (bitmap.isImmutable()) {
+        return false;
+    }
+
+    if (!hwui_flags::bitmap_parcel_ashmem_as_immutable()) {
+        return true;
+    }
+
+    // If we're going to copy the bitmap to ashmem and write that to the parcel,
+    // then parcel as immutable, since we won't be mutating the bitmap after
+    // writing it to the parcel.
+    return !shouldUseAshmem(parcel, bitmap.computeByteSize());
+}
+
 static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, jint density,
                                      jobject parcel) {
 #ifdef __linux__ // Only Linux support parcel
@@ -855,7 +882,7 @@
     auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle);
     bitmapWrapper->getSkBitmap(&bitmap);
 
-    p.writeInt32(!bitmap.isImmutable());
+    p.writeInt32(shouldParcelAsMutable(bitmap, p.get()));
     p.writeInt32(bitmap.colorType());
     p.writeInt32(bitmap.alphaType());
     SkColorSpace* colorSpace = bitmap.colorSpace();
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index cfec24b..009974b 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -53,6 +53,7 @@
 #include <src/image/SkImage_Base.h>
 #include <thread/CommonPool.h>
 #ifdef __ANDROID__
+#include <gui/SurfaceControl.h>
 #include <ui/GraphicBufferAllocator.h>
 #endif
 #include <utils/Color.h>
@@ -217,9 +218,11 @@
 
 static void android_view_ThreadedRenderer_setSurfaceControl(JNIEnv* env, jobject clazz,
         jlong proxyPtr, jlong surfaceControlPtr) {
+#ifdef __ANDROID__
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    ASurfaceControl* surfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControlPtr);
-    proxy->setSurfaceControl(surfaceControl);
+    SurfaceControl* surfaceControl = reinterpret_cast<SurfaceControl*>(surfaceControlPtr);
+    proxy->setSurfaceControl(sp<SurfaceControl>::fromExisting(surfaceControl));
+#endif
 }
 
 static jboolean android_view_ThreadedRenderer_pause(JNIEnv* env, jobject clazz,
@@ -684,7 +687,7 @@
 
 class CopyRequestAdapter : public CopyRequest {
 public:
-    CopyRequestAdapter(JavaVM* vm, jobject jCopyRequest, Rect srcRect)
+    CopyRequestAdapter(JavaVM* vm, jobject jCopyRequest, ::android::uirenderer::Rect srcRect)
             : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {}
 
     virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override {
@@ -710,8 +713,9 @@
                                                           jobject jCopyRequest) {
     JavaVM* vm = nullptr;
     LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
-    auto copyRequest = std::make_shared<CopyRequestAdapter>(vm, env->NewGlobalRef(jCopyRequest),
-                                                            Rect(left, top, right, bottom));
+    auto copyRequest = std::make_shared<CopyRequestAdapter>(
+            vm, env->NewGlobalRef(jCopyRequest),
+            ::android::uirenderer::Rect(left, top, right, bottom));
     ANativeWindow* window = fromSurface(env, jsurface);
     RenderProxy::copySurfaceInto(window, std::move(copyRequest));
     ANativeWindow_release(window);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index dc669a5..aa8cbd1 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -175,6 +175,8 @@
     if (stream->isValid()) {
         mOpenMultiPicStream = std::move(stream);
         mSerialContext.reset(new SkSharingSerialContext());
+        // passing the GrDirectContext to the SerialContext allows us to raster/serialize GPU images
+        mSerialContext->setDirectContext(mRenderThread.getGrContext());
         SkSerialProcs procs;
         procs.fImageProc = SkSharingSerialContext::serializeImage;
         procs.fImageCtx = mSerialContext.get();
diff --git a/libs/hwui/platform/host/WebViewFunctorManager.cpp b/libs/hwui/platform/host/WebViewFunctorManager.cpp
index 4ba206b..66646b2 100644
--- a/libs/hwui/platform/host/WebViewFunctorManager.cpp
+++ b/libs/hwui/platform/host/WebViewFunctorManager.cpp
@@ -45,7 +45,7 @@
 void WebViewFunctor::removeOverlays() {}
 
 ASurfaceControl* WebViewFunctor::getSurfaceControl() {
-    return mSurfaceControl;
+    return nullptr;
 }
 
 void WebViewFunctor::mergeTransaction(ASurfaceTransaction* transaction) {}
diff --git a/libs/hwui/platform/host/renderthread/RenderThread.cpp b/libs/hwui/platform/host/renderthread/RenderThread.cpp
index f9d0f47..ece4530 100644
--- a/libs/hwui/platform/host/renderthread/RenderThread.cpp
+++ b/libs/hwui/platform/host/renderthread/RenderThread.cpp
@@ -27,8 +27,6 @@
 static bool gHasRenderThreadInstance = false;
 static JVMAttachHook gOnStartHook = nullptr;
 
-ASurfaceControlFunctions::ASurfaceControlFunctions() {}
-
 bool RenderThread::hasInstance() {
     return gHasRenderThreadInstance;
 }
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b248c4b..d5ac993 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -18,6 +18,12 @@
 
 #include <apex/window.h>
 #include <fcntl.h>
+
+#ifdef __ANDROID__
+#include <gui/ITransactionCompletedListener.h>
+#include <gui/SurfaceComposerClient.h>
+#endif
+
 #include <gui/TraceUtils.h>
 #include <strings.h>
 #include <sys/stat.h>
@@ -165,7 +171,9 @@
     stopDrawing();
     setHardwareBuffer(nullptr);
     setSurface(nullptr);
+#ifdef __ANDROID__
     setSurfaceControl(nullptr);
+#endif
     freePrefetchedLayers();
     destroyHardwareResources();
     mAnimationContext->destroy();
@@ -220,10 +228,15 @@
     setupPipelineSurface();
 }
 
-void CanvasContext::setSurfaceControl(ASurfaceControl* surfaceControl) {
-    if (surfaceControl == mSurfaceControl) return;
+#ifdef __ANDROID__
+sp<SurfaceControl> CanvasContext::getSurfaceControl() const {
+    return mSurfaceControl;
+}
+#endif
 
-    auto funcs = mRenderThread.getASurfaceControlFunctions();
+void CanvasContext::setSurfaceControl(sp<SurfaceControl> surfaceControl) {
+#ifdef __ANDROID__
+    if (surfaceControl == mSurfaceControl) return;
 
     if (surfaceControl == nullptr) {
         setASurfaceTransactionCallback(nullptr);
@@ -231,17 +244,23 @@
     }
 
     if (mSurfaceControl != nullptr) {
-        funcs.unregisterListenerFunc(this, &onSurfaceStatsAvailable);
-        funcs.releaseFunc(mSurfaceControl);
+        TransactionCompletedListener::getInstance()->removeSurfaceStatsListener(
+                this, reinterpret_cast<void*>(onSurfaceStatsAvailable));
     }
-    mSurfaceControl = surfaceControl;
+
+    mSurfaceControl = std::move(surfaceControl);
     mSurfaceControlGenerationId++;
-    mExpectSurfaceStats = surfaceControl != nullptr;
+    mExpectSurfaceStats = mSurfaceControl != nullptr;
     if (mExpectSurfaceStats) {
-        funcs.acquireFunc(mSurfaceControl);
-        funcs.registerListenerFunc(surfaceControl, mSurfaceControlGenerationId, this,
-                                   &onSurfaceStatsAvailable);
+        SurfaceStatsCallback callback = [generationId = mSurfaceControlGenerationId](
+                                                void* callback_context, nsecs_t, const sp<Fence>&,
+                                                const SurfaceStats& surfaceStats) {
+            onSurfaceStatsAvailable(callback_context, generationId, surfaceStats);
+        };
+        TransactionCompletedListener::getInstance()->addSurfaceStatsListener(
+                this, reinterpret_cast<void*>(onSurfaceStatsAvailable), mSurfaceControl, callback);
     }
+#endif
 }
 
 void CanvasContext::setupPipelineSurface() {
@@ -896,17 +915,26 @@
 }
 
 void CanvasContext::onSurfaceStatsAvailable(void* context, int32_t surfaceControlId,
-                                            ASurfaceControlStats* stats) {
+                                            const SurfaceStats& stats) {
+#ifdef __ANDROID__
     auto* instance = static_cast<CanvasContext*>(context);
 
-    const ASurfaceControlFunctions& functions =
-            instance->mRenderThread.getASurfaceControlFunctions();
+    nsecs_t gpuCompleteTime = -1L;
+    if (const auto* fence = std::get_if<sp<Fence>>(&stats.acquireTimeOrFence)) {
+        // We got a fence instead of the acquire time due to latching unsignaled.
+        // Ideally the client could just get the acquire time directly from
+        // the fence instead of calling this function which needs to block.
+        (*fence)->waitForever("acquireFence");
+        gpuCompleteTime = (*fence)->getSignalTime();
+    } else {
+        gpuCompleteTime = std::get<int64_t>(stats.acquireTimeOrFence);
+    }
 
-    nsecs_t gpuCompleteTime = functions.getAcquireTimeFunc(stats);
     if (gpuCompleteTime == Fence::SIGNAL_TIME_PENDING) {
         gpuCompleteTime = -1;
     }
-    uint64_t frameNumber = functions.getFrameNumberFunc(stats);
+
+    uint64_t frameNumber = stats.eventStats.frameNumber;
 
     FrameInfo* frameInfo = instance->getFrameInfoFromLastFew(frameNumber, surfaceControlId);
 
@@ -919,6 +947,7 @@
         instance->mJankTracker.finishFrame(*frameInfo, instance->mFrameMetricsReporter, frameNumber,
                                            surfaceControlId);
     }
+#endif
 }
 
 // Called by choreographer to do an RT-driven animation
@@ -1140,10 +1169,11 @@
     return ScopedActiveContext::getActiveContext();
 }
 
-bool CanvasContext::mergeTransaction(ASurfaceTransaction* transaction, ASurfaceControl* control) {
+bool CanvasContext::mergeTransaction(ASurfaceTransaction* transaction,
+                                     const sp<SurfaceControl>& control) {
     if (!mASurfaceTransactionCallback) return false;
     return std::invoke(mASurfaceTransactionCallback, reinterpret_cast<int64_t>(transaction),
-                       reinterpret_cast<int64_t>(control), getFrameNumber());
+                       reinterpret_cast<int64_t>(control.get()), getFrameNumber());
 }
 
 void CanvasContext::prepareSurfaceControlForWebview() {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 3de8e05..655aeba 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -50,6 +50,9 @@
 #include "utils/RingBuffer.h"
 
 namespace android {
+
+class SurfaceStats;
+
 namespace uirenderer {
 
 class AnimationContext;
@@ -121,7 +124,9 @@
      */
     GrDirectContext* getGrContext() const { return mRenderThread.getGrContext(); }
 
-    ASurfaceControl* getSurfaceControl() const { return mSurfaceControl; }
+#ifdef __ANDROID__
+    sp<SurfaceControl> getSurfaceControl() const;
+#endif
     int32_t getSurfaceControlGenerationId() const { return mSurfaceControlGenerationId; }
 
     // Won't take effect until next EGLSurface creation
@@ -129,7 +134,7 @@
 
     void setHardwareBuffer(AHardwareBuffer* buffer);
     void setSurface(ANativeWindow* window, bool enableTimeout = true);
-    void setSurfaceControl(ASurfaceControl* surfaceControl);
+    void setSurfaceControl(sp<SurfaceControl> surfaceControl);
     bool pauseSurface();
     void setStopped(bool stopped);
     bool isStopped() { return mStopped || !hasOutputTarget(); }
@@ -207,7 +212,7 @@
 
     // Called when SurfaceStats are available.
     static void onSurfaceStatsAvailable(void* context, int32_t surfaceControlId,
-                                        ASurfaceControlStats* stats);
+                                        const SurfaceStats& stats);
 
     void setASurfaceTransactionCallback(
             const std::function<bool(int64_t, int64_t, int64_t)>& callback) {
@@ -218,7 +223,7 @@
         mBufferParams = params;
     }
 
-    bool mergeTransaction(ASurfaceTransaction* transaction, ASurfaceControl* control);
+    bool mergeTransaction(ASurfaceTransaction* transaction, const sp<SurfaceControl>& control);
 
     void setPrepareSurfaceControlForWebviewCallback(const std::function<void()>& callback) {
         mPrepareSurfaceControlForWebviewCallback = callback;
@@ -286,7 +291,9 @@
     std::unique_ptr<ReliableSurface> mNativeSurface;
     // The SurfaceControl reference is passed from ViewRootImpl, can be set to
     // NULL to remove the reference
-    ASurfaceControl* mSurfaceControl = nullptr;
+#ifdef __ANDROID__
+    sp<SurfaceControl> mSurfaceControl = nullptr;
+#endif
     // id to track surface control changes and WebViewFunctor uses it to determine
     // whether reparenting is needed also used by FrameMetricsReporter to determine
     // if a frame is from an "old" surface (i.e. one that existed before the
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index ebfd8fde9..e4be5fa 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -21,6 +21,11 @@
 #include <SkPicture.h>
 #include <gui/TraceUtils.h>
 #include <pthread.h>
+
+#ifdef __ANDROID__
+#include <gui/SurfaceControl.h>
+#endif
+
 #include <ui/GraphicBufferAllocator.h>
 
 #include "DeferredLayerUpdater.h"
@@ -115,17 +120,12 @@
     });
 }
 
-void RenderProxy::setSurfaceControl(ASurfaceControl* surfaceControl) {
-    auto funcs = mRenderThread.getASurfaceControlFunctions();
-    if (surfaceControl) {
-        funcs.acquireFunc(surfaceControl);
-    }
-    mRenderThread.queue().post([this, control = surfaceControl, funcs]() mutable {
-        mContext->setSurfaceControl(control);
-        if (control) {
-            funcs.releaseFunc(control);
-        }
+void RenderProxy::setSurfaceControl(sp<SurfaceControl> surfaceControl) {
+#ifdef __ANDROID__
+    mRenderThread.queue().post([this, control = std::move(surfaceControl)]() mutable {
+        mContext->setSurfaceControl(std::move(control));
     });
+#endif
 }
 
 void RenderProxy::allocateBuffers() {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index ad6d54b..23b3ebd 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -20,7 +20,6 @@
 #include <SkRefCnt.h>
 #include <android/hardware_buffer.h>
 #include <android/native_window.h>
-#include <android/surface_control.h>
 #include <cutils/compiler.h>
 #include <utils/Functor.h>
 
@@ -39,6 +38,7 @@
 
 namespace android {
 class GraphicBuffer;
+class SurfaceControl;
 class Surface;
 
 namespace uirenderer {
@@ -80,7 +80,7 @@
     void setName(const char* name);
     void setHardwareBuffer(AHardwareBuffer* buffer);
     void setSurface(ANativeWindow* window, bool enableTimeout = true);
-    void setSurfaceControl(ASurfaceControl* surfaceControl);
+    void setSurfaceControl(sp<SurfaceControl> surfaceControl);
     void allocateBuffers();
     bool pause();
     void setStopped(bool stopped);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 6ab8e4e..5e40424 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -55,66 +55,6 @@
 
 static JVMAttachHook gOnStartHook = nullptr;
 
-ASurfaceControlFunctions::ASurfaceControlFunctions() {
-    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
-    createFunc = (ASC_create)dlsym(handle_, "ASurfaceControl_create");
-    LOG_ALWAYS_FATAL_IF(createFunc == nullptr,
-                        "Failed to find required symbol ASurfaceControl_create!");
-
-    acquireFunc = (ASC_acquire) dlsym(handle_, "ASurfaceControl_acquire");
-    LOG_ALWAYS_FATAL_IF(acquireFunc == nullptr,
-            "Failed to find required symbol ASurfaceControl_acquire!");
-
-    releaseFunc = (ASC_release) dlsym(handle_, "ASurfaceControl_release");
-    LOG_ALWAYS_FATAL_IF(releaseFunc == nullptr,
-            "Failed to find required symbol ASurfaceControl_release!");
-
-    registerListenerFunc = (ASC_registerSurfaceStatsListener) dlsym(handle_,
-            "ASurfaceControl_registerSurfaceStatsListener");
-    LOG_ALWAYS_FATAL_IF(registerListenerFunc == nullptr,
-            "Failed to find required symbol ASurfaceControl_registerSurfaceStatsListener!");
-
-    unregisterListenerFunc = (ASC_unregisterSurfaceStatsListener) dlsym(handle_,
-            "ASurfaceControl_unregisterSurfaceStatsListener");
-    LOG_ALWAYS_FATAL_IF(unregisterListenerFunc == nullptr,
-            "Failed to find required symbol ASurfaceControl_unregisterSurfaceStatsListener!");
-
-    getAcquireTimeFunc = (ASCStats_getAcquireTime) dlsym(handle_,
-            "ASurfaceControlStats_getAcquireTime");
-    LOG_ALWAYS_FATAL_IF(getAcquireTimeFunc == nullptr,
-            "Failed to find required symbol ASurfaceControlStats_getAcquireTime!");
-
-    getFrameNumberFunc = (ASCStats_getFrameNumber) dlsym(handle_,
-            "ASurfaceControlStats_getFrameNumber");
-    LOG_ALWAYS_FATAL_IF(getFrameNumberFunc == nullptr,
-            "Failed to find required symbol ASurfaceControlStats_getFrameNumber!");
-
-    transactionCreateFunc = (AST_create)dlsym(handle_, "ASurfaceTransaction_create");
-    LOG_ALWAYS_FATAL_IF(transactionCreateFunc == nullptr,
-                        "Failed to find required symbol ASurfaceTransaction_create!");
-
-    transactionDeleteFunc = (AST_delete)dlsym(handle_, "ASurfaceTransaction_delete");
-    LOG_ALWAYS_FATAL_IF(transactionDeleteFunc == nullptr,
-                        "Failed to find required symbol ASurfaceTransaction_delete!");
-
-    transactionApplyFunc = (AST_apply)dlsym(handle_, "ASurfaceTransaction_apply");
-    LOG_ALWAYS_FATAL_IF(transactionApplyFunc == nullptr,
-                        "Failed to find required symbol ASurfaceTransaction_apply!");
-
-    transactionReparentFunc = (AST_reparent)dlsym(handle_, "ASurfaceTransaction_reparent");
-    LOG_ALWAYS_FATAL_IF(transactionReparentFunc == nullptr,
-                        "Failed to find required symbol transactionReparentFunc!");
-
-    transactionSetVisibilityFunc =
-            (AST_setVisibility)dlsym(handle_, "ASurfaceTransaction_setVisibility");
-    LOG_ALWAYS_FATAL_IF(transactionSetVisibilityFunc == nullptr,
-                        "Failed to find required symbol ASurfaceTransaction_setVisibility!");
-
-    transactionSetZOrderFunc = (AST_setZOrder)dlsym(handle_, "ASurfaceTransaction_setZOrder");
-    LOG_ALWAYS_FATAL_IF(transactionSetZOrderFunc == nullptr,
-                        "Failed to find required symbol ASurfaceTransaction_setZOrder!");
-}
-
 void RenderThread::extendedFrameCallback(const AChoreographerFrameCallbackData* cbData,
                                          void* data) {
     RenderThread* rt = reinterpret_cast<RenderThread*>(data);
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 86fddba..f733c7c 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -77,49 +77,6 @@
     virtual ~VsyncSource() {}
 };
 
-typedef ASurfaceControl* (*ASC_create)(ASurfaceControl* parent, const char* debug_name);
-typedef void (*ASC_acquire)(ASurfaceControl* control);
-typedef void (*ASC_release)(ASurfaceControl* control);
-
-typedef void (*ASC_registerSurfaceStatsListener)(ASurfaceControl* control, int32_t id,
-                                                 void* context,
-                                                 ASurfaceControl_SurfaceStatsListener func);
-typedef void (*ASC_unregisterSurfaceStatsListener)(void* context,
-                                                   ASurfaceControl_SurfaceStatsListener func);
-
-typedef int64_t (*ASCStats_getAcquireTime)(ASurfaceControlStats* stats);
-typedef uint64_t (*ASCStats_getFrameNumber)(ASurfaceControlStats* stats);
-
-typedef ASurfaceTransaction* (*AST_create)();
-typedef void (*AST_delete)(ASurfaceTransaction* transaction);
-typedef void (*AST_apply)(ASurfaceTransaction* transaction);
-typedef void (*AST_reparent)(ASurfaceTransaction* aSurfaceTransaction,
-                             ASurfaceControl* aSurfaceControl,
-                             ASurfaceControl* newParentASurfaceControl);
-typedef void (*AST_setVisibility)(ASurfaceTransaction* transaction,
-                                  ASurfaceControl* surface_control, int8_t visibility);
-typedef void (*AST_setZOrder)(ASurfaceTransaction* transaction, ASurfaceControl* surface_control,
-                              int32_t z_order);
-
-struct ASurfaceControlFunctions {
-    ASurfaceControlFunctions();
-
-    ASC_create createFunc;
-    ASC_acquire acquireFunc;
-    ASC_release releaseFunc;
-    ASC_registerSurfaceStatsListener registerListenerFunc;
-    ASC_unregisterSurfaceStatsListener unregisterListenerFunc;
-    ASCStats_getAcquireTime getAcquireTimeFunc;
-    ASCStats_getFrameNumber getFrameNumberFunc;
-
-    AST_create transactionCreateFunc;
-    AST_delete transactionDeleteFunc;
-    AST_apply transactionApplyFunc;
-    AST_reparent transactionReparentFunc;
-    AST_setVisibility transactionSetVisibilityFunc;
-    AST_setZOrder transactionSetZOrderFunc;
-};
-
 class ChoreographerSource;
 class DummyVsyncSource;
 
@@ -166,10 +123,6 @@
 
     void preload();
 
-    const ASurfaceControlFunctions& getASurfaceControlFunctions() {
-        return mASurfaceControlFunctions;
-    }
-
     void trimMemory(TrimLevel level);
     void trimCaches(CacheTrimLevel level);
 
@@ -244,7 +197,6 @@
     CacheManager* mCacheManager;
     sp<VulkanManager> mVkManager;
 
-    ASurfaceControlFunctions mASurfaceControlFunctions;
     std::mutex mJankDataMutex;
 };
 
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index 56d3df3..311d64f 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -29,6 +29,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -52,6 +53,127 @@
 
     private static final String TAG = "AudioDeviceVolumeManager";
 
+    /**
+     * @hide
+     * Volume behavior for an audio device that has no particular volume behavior set. Invalid as
+     * an argument to {@link #setDeviceVolumeBehavior(AudioDeviceAttributes, int)} and should not
+     * be returned by {@link #getDeviceVolumeBehavior(AudioDeviceAttributes)}.
+     */
+    public static final int DEVICE_VOLUME_BEHAVIOR_UNSET = -1;
+    /**
+     * @hide
+     * Volume behavior for an audio device where a software attenuation is applied
+     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
+    public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0;
+    /**
+     * @hide
+     * Volume behavior for an audio device where the volume is always set to provide no attenuation
+     *     nor gain (e.g. unit gain).
+     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
+    public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1;
+    /**
+     * @hide
+     * Volume behavior for an audio device where the volume is either set to muted, or to provide
+     *     no attenuation nor gain (e.g. unit gain).
+     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
+    public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2;
+    /**
+     * @hide
+     * Volume behavior for an audio device where no software attenuation is applied, and
+     *     the volume is kept synchronized between the host and the device itself through a
+     *     device-specific protocol such as BT AVRCP.
+     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
+    public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3;
+    /**
+     * @hide
+     * Volume behavior for an audio device where no software attenuation is applied, and
+     *     the volume is kept synchronized between the host and the device itself through a
+     *     device-specific protocol (such as for hearing aids), based on the audio mode (e.g.
+     *     normal vs in phone call).
+     * @see AudioManager#setMode(int)
+     * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
+    public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4;
+
+    /**
+     * @hide
+     * A variant of {@link #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE} where the host cannot reliably set
+     * the volume percentage of the audio device. Specifically, {@link AudioManager#setStreamVolume}
+     * will have no effect, or an unreliable effect.
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
+    public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5;
+
+    /** @hide */
+    @IntDef({
+            DEVICE_VOLUME_BEHAVIOR_VARIABLE,
+            DEVICE_VOLUME_BEHAVIOR_FULL,
+            DEVICE_VOLUME_BEHAVIOR_FIXED,
+            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE,
+            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE,
+            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DeviceVolumeBehavior {}
+
+    /** @hide */
+    @IntDef({
+            DEVICE_VOLUME_BEHAVIOR_UNSET,
+            DEVICE_VOLUME_BEHAVIOR_VARIABLE,
+            DEVICE_VOLUME_BEHAVIOR_FULL,
+            DEVICE_VOLUME_BEHAVIOR_FIXED,
+            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE,
+            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE,
+            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DeviceVolumeBehaviorState {}
+
+    /**
+     * Variants of absolute volume behavior that are set in for absolute volume management.
+     * @hide
+     */
+    @IntDef({
+            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE,
+            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AbsoluteDeviceVolumeBehavior {}
+
+    /**
+     * @hide
+     * Throws IAE on an invalid volume behavior value
+     * @param volumeBehavior behavior value to check
+     */
+    public static void enforceValidVolumeBehavior(int volumeBehavior) {
+        switch (volumeBehavior) {
+            case DEVICE_VOLUME_BEHAVIOR_VARIABLE:
+            case DEVICE_VOLUME_BEHAVIOR_FULL:
+            case DEVICE_VOLUME_BEHAVIOR_FIXED:
+            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
+            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
+            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY:
+                return;
+            default:
+                throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior);
+        }
+    }
+
     /** @hide
      * Indicates no special treatment in the handling of the volume adjustment */
     public static final int ADJUST_MODE_NORMAL = 0;
@@ -158,7 +280,7 @@
                 android.Manifest.permission.BLUETOOTH_PRIVILEGED })
         public void register(boolean register, @NonNull AudioDeviceAttributes device,
                 @NonNull List<VolumeInfo> volumes, boolean handlesVolumeAdjustment,
-                @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) {
+                @AbsoluteDeviceVolumeBehavior int behavior) {
             try {
                 getService().registerDeviceVolumeDispatcherForAbsoluteVolume(register,
                         this, mPackageName,
@@ -204,6 +326,94 @@
 
     /**
      * @hide
+     * Sets the volume behavior for an audio output device.
+     * @see #DEVICE_VOLUME_BEHAVIOR_VARIABLE
+     * @see #DEVICE_VOLUME_BEHAVIOR_FULL
+     * @see #DEVICE_VOLUME_BEHAVIOR_FIXED
+     * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE
+     * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE
+     * @param device the device to be affected
+     * @param deviceVolumeBehavior one of the device behaviors
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MODIFY_AUDIO_ROUTING,
+            Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
+    })
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
+    public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
+            @DeviceVolumeBehavior int deviceVolumeBehavior) {
+        // verify arguments (validity of device type is enforced in server)
+        Objects.requireNonNull(device);
+        enforceValidVolumeBehavior(deviceVolumeBehavior);
+        // communicate with service
+        final IAudioService service = getService();
+        try {
+            service.setDeviceVolumeBehavior(device, deviceVolumeBehavior, mPackageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Returns the volume device behavior for the given audio device
+     * @param device the audio device
+     * @return the volume behavior for the device
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MODIFY_AUDIO_ROUTING,
+            Manifest.permission.QUERY_AUDIO_STATE,
+            Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
+    })
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
+    public @DeviceVolumeBehavior int getDeviceVolumeBehavior(
+            @NonNull AudioDeviceAttributes device) {
+        // verify arguments (validity of device type is enforced in server)
+        Objects.requireNonNull(device);
+        // communicate with service
+        final IAudioService service = getService();
+        try {
+            return service.getDeviceVolumeBehavior(device);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Returns {@code true} if the volume device behavior is {@link #DEVICE_VOLUME_BEHAVIOR_FULL}.
+     */
+    @TestApi
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MODIFY_AUDIO_ROUTING,
+            Manifest.permission.QUERY_AUDIO_STATE,
+            Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
+    })
+    @SuppressWarnings("UnflaggedApi")  // @TestApi without associated feature.
+    public boolean isFullVolumeDevice() {
+        final AudioAttributes attributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .build();
+        List<AudioDeviceAttributes> devices;
+        final IAudioService service = getService();
+        try {
+            devices = service.getDevicesForAttributes(attributes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        for (AudioDeviceAttributes device : devices) {
+            if (getDeviceVolumeBehavior(device) == DEVICE_VOLUME_BEHAVIOR_FULL) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @hide
      * Configures a device to use absolute volume model, and registers a listener for receiving
      * volume updates to apply on that device
      * @param device the audio device set to absolute volume mode
@@ -297,7 +507,7 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OnAudioDeviceVolumeChangedListener vclistener) {
         baseSetDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener,
-                handlesVolumeAdjustment, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+                handlesVolumeAdjustment, DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
     }
 
     /**
@@ -355,12 +565,12 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OnAudioDeviceVolumeChangedListener vclistener) {
         baseSetDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener,
-                handlesVolumeAdjustment, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+                handlesVolumeAdjustment, DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
     }
 
     /**
      * Base method for configuring a device to use absolute volume behavior, or one of its variants.
-     * See {@link AudioManager.AbsoluteDeviceVolumeBehavior} for a list of allowed behaviors.
+     * See {@link AbsoluteDeviceVolumeBehavior} for a list of allowed behaviors.
      *
      * @param behavior the variant of absolute device volume behavior to adopt
      */
@@ -372,7 +582,7 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OnAudioDeviceVolumeChangedListener vclistener,
             boolean handlesVolumeAdjustment,
-            @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) {
+            @AbsoluteDeviceVolumeBehavior int behavior) {
         Objects.requireNonNull(device);
         Objects.requireNonNull(volumes);
         Objects.requireNonNull(executor);
@@ -417,7 +627,7 @@
          */
         void onDeviceVolumeBehaviorChanged(
                 @NonNull AudioDeviceAttributes device,
-                @AudioManager.DeviceVolumeBehavior int volumeBehavior);
+                @DeviceVolumeBehavior int volumeBehavior);
     }
 
     /**
@@ -580,19 +790,19 @@
      * @param behavior one of the volume behaviors defined in AudioManager
      * @return a string for the given behavior
      */
-    public static String volumeBehaviorName(@AudioManager.DeviceVolumeBehavior int behavior) {
+    public static String volumeBehaviorName(@DeviceVolumeBehavior int behavior) {
         switch (behavior) {
-            case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE:
+            case DEVICE_VOLUME_BEHAVIOR_VARIABLE:
                 return "DEVICE_VOLUME_BEHAVIOR_VARIABLE";
-            case AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL:
+            case DEVICE_VOLUME_BEHAVIOR_FULL:
                 return "DEVICE_VOLUME_BEHAVIOR_FULL";
-            case AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED:
+            case DEVICE_VOLUME_BEHAVIOR_FIXED:
                 return "DEVICE_VOLUME_BEHAVIOR_FIXED";
-            case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
+            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
                 return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE";
-            case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
+            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
                 return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE";
-            case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY:
+            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY:
                 return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY";
             default:
                 return "invalid volume behavior " + behavior;
@@ -611,7 +821,7 @@
 
         @Override
         public void dispatchDeviceVolumeBehaviorChanged(@NonNull AudioDeviceAttributes device,
-                @AudioManager.DeviceVolumeBehavior int volumeBehavior) {
+                @DeviceVolumeBehavior int volumeBehavior) {
             mDeviceVolumeBehaviorChangedListenerMgr.callListeners((listener) ->
                     listener.onDeviceVolumeBehaviorChanged(device, volumeBehavior));
         }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 32af7c6..4aba491 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -19,6 +19,7 @@
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
 import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.media.audio.Flags.FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT;
 import static android.media.audio.Flags.autoPublicVolumeApiHardening;
 import static android.media.audio.Flags.cacheGetStreamMinMaxVolume;
 import static android.media.audio.Flags.cacheGetStreamVolume;
@@ -6659,24 +6660,30 @@
      * @hide
      * Volume behavior for an audio device where a software attenuation is applied
      * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
+     * @deprecated use {@link AudioDeviceVolumeManager#DEVICE_VOLUME_BEHAVIOR_VARIABLE} instead
      */
     @SystemApi
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
     public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0;
     /**
      * @hide
      * Volume behavior for an audio device where the volume is always set to provide no attenuation
      *     nor gain (e.g. unit gain).
      * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
+     * @deprecated use {@link AudioDeviceVolumeManager#DEVICE_VOLUME_BEHAVIOR_FULL} instead
      */
     @SystemApi
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
     public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1;
     /**
      * @hide
      * Volume behavior for an audio device where the volume is either set to muted, or to provide
      *     no attenuation nor gain (e.g. unit gain).
      * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
+     * @deprecated use {@link AudioDeviceVolumeManager#DEVICE_VOLUME_BEHAVIOR_FIXED} instead
      */
     @SystemApi
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
     public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2;
     /**
      * @hide
@@ -6684,8 +6691,10 @@
      *     the volume is kept synchronized between the host and the device itself through a
      *     device-specific protocol such as BT AVRCP.
      * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
+     * @deprecated use {@link AudioDeviceVolumeManager#DEVICE_VOLUME_BEHAVIOR_ABSOLUTE} instead
      */
     @SystemApi
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
     public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3;
     /**
      * @hide
@@ -6695,8 +6704,11 @@
      *     normal vs in phone call).
      * @see #setMode(int)
      * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
+     * @deprecated use {@link AudioDeviceVolumeManager#DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE}
+     * instead
      */
     @SystemApi
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
     public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4;
 
     /**
@@ -6704,8 +6716,11 @@
      * A variant of {@link #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE} where the host cannot reliably set
      * the volume percentage of the audio device. Specifically, {@link #setStreamVolume} will have
      * no effect, or an unreliable effect.
+     * @deprecated use {@link AudioDeviceVolumeManager#DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY}
+     * instead
      */
     @SystemApi
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
     public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5;
 
     /** @hide */
@@ -6720,49 +6735,6 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface DeviceVolumeBehavior {}
 
-    /** @hide */
-    @IntDef({
-            DEVICE_VOLUME_BEHAVIOR_UNSET,
-            DEVICE_VOLUME_BEHAVIOR_VARIABLE,
-            DEVICE_VOLUME_BEHAVIOR_FULL,
-            DEVICE_VOLUME_BEHAVIOR_FIXED,
-            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE,
-            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE,
-            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface DeviceVolumeBehaviorState {}
-
-    /**
-     * Variants of absolute volume behavior that are set in {@link AudioDeviceVolumeManager}.
-     * @hide
-     */
-    @IntDef({
-            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE,
-            DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface AbsoluteDeviceVolumeBehavior {}
-
-    /**
-     * @hide
-     * Throws IAE on an invalid volume behavior value
-     * @param volumeBehavior behavior value to check
-     */
-    public static void enforceValidVolumeBehavior(int volumeBehavior) {
-        switch (volumeBehavior) {
-            case DEVICE_VOLUME_BEHAVIOR_VARIABLE:
-            case DEVICE_VOLUME_BEHAVIOR_FULL:
-            case DEVICE_VOLUME_BEHAVIOR_FIXED:
-            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
-            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
-            case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY:
-                return;
-            default:
-                throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior);
-        }
-    }
-
     /**
      * @hide
      * Sets the volume behavior for an audio output device.
@@ -6773,17 +6745,21 @@
      * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE
      * @param device the device to be affected
      * @param deviceVolumeBehavior one of the device behaviors
+     *
+     * @deprecated use
+     * {@link AudioDeviceVolumeManager#setDeviceVolumeBehavior(AudioDeviceAttributes, int)} instead
      */
     @SystemApi
     @RequiresPermission(anyOf = {
             Manifest.permission.MODIFY_AUDIO_ROUTING,
             Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
     })
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
     public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
             @DeviceVolumeBehavior int deviceVolumeBehavior) {
         // verify arguments (validity of device type is enforced in server)
         Objects.requireNonNull(device);
-        enforceValidVolumeBehavior(deviceVolumeBehavior);
+        AudioDeviceVolumeManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
         // communicate with service
         final IAudioService service = getService();
         try {
@@ -6810,6 +6786,8 @@
      * Returns the volume device behavior for the given audio device
      * @param device the audio device
      * @return the volume behavior for the device
+     * @deprecated use
+     * {@link AudioDeviceVolumeManager#getDeviceVolumeBehavior(AudioDeviceAttributes)} instead
      */
     @SystemApi
     @RequiresPermission(anyOf = {
@@ -6817,6 +6795,7 @@
             Manifest.permission.QUERY_AUDIO_STATE,
             Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
     })
+    @FlaggedApi(FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT)
     public @DeviceVolumeBehavior
     int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
         // verify arguments (validity of device type is enforced in server)
@@ -6836,29 +6815,6 @@
     }
 
     /**
-     * @hide
-     * Returns {@code true} if the volume device behavior is {@link #DEVICE_VOLUME_BEHAVIOR_FULL}.
-     */
-    @TestApi
-    @RequiresPermission(anyOf = {
-            Manifest.permission.MODIFY_AUDIO_ROUTING,
-            Manifest.permission.QUERY_AUDIO_STATE,
-            Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
-    })
-    public boolean isFullVolumeDevice() {
-        final AudioAttributes attributes = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_MEDIA)
-                .build();
-        final List<AudioDeviceAttributes> devices = getDevicesForAttributes(attributes);
-        for (AudioDeviceAttributes device : devices) {
-            if (getDeviceVolumeBehavior(device) == DEVICE_VOLUME_BEHAVIOR_FULL) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
      * Indicate wired accessory connection state change.
      * @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx)
      * @param state  new connection state: 1 connected, 0 disconnected
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index f3b21bf..3b560b7 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -219,13 +219,14 @@
     private static final int DEFAULT_MAX_SUPPORTED_INSTANCES = 32;
     private static final int MAX_SUPPORTED_INSTANCES_LIMIT = 256;
 
-    private static final class LazyHolder {
-        private static final Range<Integer> SIZE_RANGE = Process.is64Bit()
-                ? Range.create(1, 32768)
-                : Range.create(1, MediaProperties.resolution_limit_32bit().orElse(4096));
-    }
-    private static Range<Integer> getSizeRange() {
-        return LazyHolder.SIZE_RANGE;
+    private static Range<Integer> SIZE_RANGE;
+    private static synchronized Range<Integer> getSizeRange() {
+        if (SIZE_RANGE == null) {
+            SIZE_RANGE = Process.is64Bit()
+                    ? Range.create(1, 32768)
+                    : Range.create(1, MediaProperties.resolution_limit_32bit().orElse(4096));
+        }
+        return SIZE_RANGE;
     }
 
     // found stuff that is not supported by framework (=> this should not happen)
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 7af78b8..03bcc6a 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -1299,6 +1299,7 @@
      * start() or before setOutputFormat().
      * @throws IOException if prepare fails otherwise.
      */
+    @RequiresPermission(value = android.Manifest.permission.RECORD_AUDIO, conditional = true)
     public void prepare() throws IllegalStateException, IOException
     {
         if (mPath != null) {
@@ -1337,6 +1338,7 @@
      * @throws IllegalStateException if it is called before
      * prepare() or when the camera is already in use by another app.
      */
+    @RequiresPermission(value = android.Manifest.permission.RECORD_AUDIO, conditional = true)
     public native void start() throws IllegalStateException;
 
     /**
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
index be711ac..89e5372 100644
--- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -27,6 +27,7 @@
 import android.widget.LinearLayout;
 
 import androidx.annotation.GravityInt;
+import androidx.annotation.IntDef;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
@@ -34,21 +35,46 @@
 
 import com.google.android.material.button.MaterialButton;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A preference handled a button
  */
 public class ButtonPreference extends Preference implements GroupSectionDividerMixin {
 
+    public static final int TYPE_FILLED = 0;
+    public static final int TYPE_TONAL = 1;
+    public static final int TYPE_OUTLINE = 2;
+
+    @IntDef({TYPE_FILLED, TYPE_TONAL, TYPE_OUTLINE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {
+    }
+
+    public static final int SIZE_NORMAL = 0;
+    public static final int SIZE_LARGE = 1;
+    public static final int SIZE_EXTRA_LARGE = 2;
+
+    @IntDef({SIZE_NORMAL, SIZE_LARGE, SIZE_EXTRA_LARGE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Size {
+    }
+
     enum ButtonStyle {
-        FILLED_NORMAL(0, 0, R.layout.settingslib_expressive_button_filled),
-        FILLED_LARGE(0, 1, R.layout.settingslib_expressive_button_filled_large),
-        FILLED_EXTRA(0, 2, R.layout.settingslib_expressive_button_filled_extra),
-        TONAL_NORMAL(1, 0, R.layout.settingslib_expressive_button_tonal),
-        TONAL_LARGE(1, 1, R.layout.settingslib_expressive_button_tonal_large),
-        TONAL_EXTRA(1, 2, R.layout.settingslib_expressive_button_tonal_extra),
-        OUTLINE_NORMAL(2, 0, R.layout.settingslib_expressive_button_outline),
-        OUTLINE_LARGE(2, 1, R.layout.settingslib_expressive_button_outline_large),
-        OUTLINE_EXTRA(2, 2, R.layout.settingslib_expressive_button_outline_extra);
+        FILLED_NORMAL(TYPE_FILLED, SIZE_NORMAL, R.layout.settingslib_expressive_button_filled),
+        FILLED_LARGE(TYPE_FILLED, SIZE_LARGE, R.layout.settingslib_expressive_button_filled_large),
+        FILLED_EXTRA(TYPE_FILLED, SIZE_EXTRA_LARGE,
+                R.layout.settingslib_expressive_button_filled_extra),
+        TONAL_NORMAL(TYPE_TONAL, SIZE_NORMAL, R.layout.settingslib_expressive_button_tonal),
+        TONAL_LARGE(TYPE_TONAL, SIZE_LARGE, R.layout.settingslib_expressive_button_tonal_large),
+        TONAL_EXTRA(TYPE_TONAL, SIZE_EXTRA_LARGE,
+                R.layout.settingslib_expressive_button_tonal_extra),
+        OUTLINE_NORMAL(TYPE_OUTLINE, SIZE_NORMAL, R.layout.settingslib_expressive_button_outline),
+        OUTLINE_LARGE(TYPE_OUTLINE, SIZE_LARGE,
+                R.layout.settingslib_expressive_button_outline_large),
+        OUTLINE_EXTRA(TYPE_OUTLINE, SIZE_EXTRA_LARGE,
+                R.layout.settingslib_expressive_button_outline_extra);
 
         private final int mType;
         private final int mSize;
@@ -60,7 +86,7 @@
             this.mLayoutId = layoutId;
         }
 
-        static int getLayoutId(int type, int size) {
+        static int getLayoutId(@Type int type, @Size int size) {
             for (ButtonStyle style : values()) {
                 if (style.mType == type && style.mSize == size) {
                     return style.mLayoutId;
@@ -266,7 +292,7 @@
      *                 <li>2: extra large</li>
      *             </ul>
      */
-    public void setButtonStyle(int type, int size) {
+    public void setButtonStyle(@Type int type, @Size int size) {
         int layoutId = ButtonStyle.getLayoutId(type, size);
         setLayoutResource(layoutId);
         notifyChanged();
diff --git a/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml b/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml
new file mode 100644
index 0000000..6b534aa
--- /dev/null
+++ b/packages/SettingsLib/StatusBannerPreference/res/drawable/settingslib_expressive_icon_status_level_off.xml
@@ -0,0 +1,28 @@
+<?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="34dp"
+    android:height="42dp"
+    android:viewportWidth="34"
+    android:viewportHeight="42">
+    <path
+        android:pathData="M0.856,17.569C0.887,19.083 1.004,20.593 1.206,22.094C2.166,28.584 5.804,35.937 15.774,41.089C16.171,41.293 16.61,41.4 17.056,41.4C17.503,41.4 17.942,41.293 18.339,41.089C28.309,35.936 31.947,28.583 32.907,22.093C33.109,20.593 33.226,19.083 33.256,17.569V8.605C33.257,7.919 33.046,7.25 32.652,6.688C32.259,6.127 31.703,5.7 31.059,5.467L18.191,0.8C17.458,0.534 16.655,0.534 15.922,0.8L3.054,5.467C2.41,5.7 1.854,6.127 1.461,6.688C1.067,7.25 0.856,7.919 0.856,8.605V17.569Z"
+        android:fillColor="#D1C2CB"/>
+    <path
+        android:pathData="M15.067,24.333V10.733H18.933V24.333H15.067ZM15.067,31.267V27.433H18.933V31.267H15.067Z"
+        android:fillColor="@color/settingslib_materialColorSurfaceContainerLowest"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml b/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml
index 54860d4..deda258 100644
--- a/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml
+++ b/packages/SettingsLib/StatusBannerPreference/res/values/attrs.xml
@@ -21,6 +21,7 @@
             <enum name="low" value="1"/>
             <enum name="medium" value="2"/>
             <enum name="high" value="3"/>
+            <enum name="off" value="4"/>
         </attr>
         <attr name="buttonLevel" format="enum">
             <enum name="generic" value="0"/>
diff --git a/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml b/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml
index 19181dd..abc458b 100644
--- a/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml
+++ b/packages/SettingsLib/StatusBannerPreference/res/values/colors.xml
@@ -22,4 +22,5 @@
     <color name="settingslib_expressive_color_status_level_medium">#FCBD00</color>
     <!-- static palette red50 -->
     <color name="settingslib_expressive_color_status_level_high">#DB372D</color>
+    <color name="settingslib_expressive_color_status_level_off">#D1C2CB</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
index 1f8cfb5..eda281c 100644
--- a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
+++ b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
@@ -40,7 +40,8 @@
         GENERIC,
         LOW,
         MEDIUM,
-        HIGH
+        HIGH,
+        OFF
     }
     var iconLevel: BannerStatus = BannerStatus.GENERIC
         set(value) {
@@ -87,6 +88,7 @@
         1 -> BannerStatus.LOW
         2 -> BannerStatus.MEDIUM
         3 -> BannerStatus.HIGH
+        4 -> BannerStatus.OFF
         else -> BannerStatus.GENERIC
     }
 
@@ -104,7 +106,10 @@
         }
 
         (holder.findViewById(R.id.status_banner_button) as? MaterialButton)?.apply {
-            setBackgroundColor(getBackgroundColor(buttonLevel))
+            setBackgroundColor(
+                if (buttonLevel == BannerStatus.OFF) getBackgroundColor(BannerStatus.GENERIC)
+                else getBackgroundColor(buttonLevel)
+            )
             text = buttonText
             setOnClickListener(listener)
             visibility = if (listener != null) View.VISIBLE else View.GONE
@@ -143,6 +148,11 @@
                 R.color.settingslib_expressive_color_status_level_high
             )
 
+            BannerStatus.OFF -> ContextCompat.getColor(
+                context,
+                R.color.settingslib_expressive_color_status_level_off
+            )
+
             else -> ContextCompat.getColor(
                 context,
                 com.android.settingslib.widget.theme.R.color.settingslib_materialColorPrimary
@@ -167,6 +177,11 @@
                 R.drawable.settingslib_expressive_icon_status_level_high
             )
 
+            BannerStatus.OFF -> ContextCompat.getDrawable(
+                context,
+                R.drawable.settingslib_expressive_icon_status_level_off
+            )
+
             else -> null
         }
     }
@@ -188,6 +203,7 @@
                 R.drawable.settingslib_expressive_background_level_high
             )
 
+            // GENERIC and OFF are using the same background drawable.
             else -> ContextCompat.getDrawable(
                 context,
                 R.drawable.settingslib_expressive_background_generic
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index 7f4bebc..3355266 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -46,6 +46,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import java.util.ArrayList;
@@ -326,6 +328,15 @@
         return false;
     }
 
+    /** Returns the icon color scheme. */
+    @Nullable
+    public String getIconColorScheme(@NonNull Context context) {
+        ensureMetadataNotStale(context);
+        return mMetaData != null
+                ? mMetaData.getString(TileUtils.META_DATA_PREFERENCE_ICON_COLOR_SCHEME, null)
+                : null;
+    }
+
     /** Whether the {@link Activity} should be launched in a separate task. */
     public boolean isNewTask() {
         if (mMetaData != null && mMetaData.containsKey(META_DATA_NEW_TASK)) {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index ac0b9b4..d62ed2f 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -135,6 +135,13 @@
     public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
 
     /**
+     * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the icon
+     * color scheme. Only available for preferences on the homepage.
+     */
+    public static final String META_DATA_PREFERENCE_ICON_COLOR_SCHEME =
+            "com.android.settings.icon_color_scheme";
+
+    /**
      * Name of the meta-data item that should be set in the AndroidManifest.xml
      * to specify the icon background color. The value may or may not be used by Settings app.
      */
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 55f7317..758ad79 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -1015,6 +1015,10 @@
     <uses-permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE" />
     <uses-permission android:name="android.permission.READ_COLOR_ZONES" />
 
+    <!-- Permissions required for CTS test - CtsModernMediaProviderTests -->
+    <uses-permission android:name="com.android.providers.media.permission.ACCESS_OEM_METADATA" />
+    <uses-permission android:name="com.android.providers.media.permission.UPDATE_OEM_METADATA" />
+
     <!-- Permission required for trade-in mode testing -->
     <uses-permission android:name="android.permission.ENTER_TRADE_IN_MODE" />
 
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 1362ffe..86559fd 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -1,22 +1,6 @@
 {
-  // Curious where your @Scenario tests are running?
-  //
-  // @Ignore: Will not run in any configuration
-  //
-  // @FlakyTest: Tests that don't block pre/postsubmit but are staged to run known failures.
-  //             Tests will run in postsubmit on sysui-e2e-staged suite.
-  //
-  //
-  // @PlatinumTest: Marking your test with this annotation will put your tests in presubmit.
-  //                Please DO NOT annotate new or old tests with @PlatinumTest annotation
-  //                without discussing with mdb:android-platinum
-  //
-  // @Postsubmit: Do not use this annotation for e2e tests. This won't have any affect.
-
-  // For all other e2e tests which are not platinum, they run in sysui-silver suite,that
-  // primarily runs in postsubmit with an exception to e2e test related changes.
-  // If you want to see one shot place to monitor all e2e tests, look for
-  // sysui-e2e-staged suite.
+  // Test mappings for SystemUI unit tests.
+  // For e2e mappings, see go/sysui-e2e-test-mapping
 
   // v2/android-virtual-infra/test_mapping/presubmit-avd
   "presubmit": [
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 4693377..cf81154 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -398,13 +398,6 @@
 }
 
 flag {
-    name: "light_reveal_migration"
-    namespace: "systemui"
-    description: "Move LightRevealScrim to recommended architecture"
-    bug: "281655028"
-}
-
-flag {
    name: "theme_overlay_controller_wakefulness_deprecation"
    namespace: "systemui"
    description: "Replacing WakefulnessLifecycle by KeyguardTransitionInteractor in "
@@ -1443,16 +1436,6 @@
 }
 
 flag {
-  name: "dozeui_scheduling_alarms_background_execution"
-  namespace: "systemui"
-  description: "Decide whether to execute binder calls to schedule alarms in background thread"
-  bug: "330492575"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
     name: "media_lockscreen_launch_animation"
     namespace : "systemui"
     description : "Enable the origin launch animation for UMO when opening on top of lockscreen."
@@ -2136,3 +2119,14 @@
     description: "Enables return animations for status bar chips"
     bug: "202516970"
 }
+
+flag {
+    name: "media_projection_grey_error_text"
+    namespace: "systemui"
+    description: "Set the error text color to grey when app sharing is hidden by the requesting app"
+    bug: "390624334"
+    metadata {
+       purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
index ca94482..21ec896 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
@@ -245,6 +245,17 @@
                 runner.onAnimationCancelled();
                 finishRunnable.run();
             }
+
+            @Override
+            public void onTransitionConsumed(IBinder transition, boolean aborted)
+                    throws RemoteException {
+                // Notify the remote runner that the transition has been canceled if the transition
+                // was merged into another transition or aborted
+                synchronized (mFinishRunnables) {
+                    mFinishRunnables.remove(transition);
+                }
+                runner.onAnimationCancelled();
+            }
         };
     }
 
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index 82e5f5b..cd9fcef 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -127,6 +127,8 @@
  *
  * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
  * @sample com.android.systemui.compose.gallery.DialogLaunchScreen
+ * @param defaultMinSize true if a default minimum size should be enforced even if this Expandable
+ *   isn't currently clickable and false otherwise.
  */
 @Composable
 fun Expandable(
@@ -140,6 +142,7 @@
     // TODO(b/285250939): Default this to true then remove once the Compose QS expandables have
     // proven that the new implementation is robust.
     useModifierBasedImplementation: Boolean = false,
+    defaultMinSize: Boolean = true,
     transitionControllerFactory: ComposableControllerFactory? = null,
     content: @Composable (Expandable) -> Unit,
 ) {
@@ -155,6 +158,7 @@
         onClick,
         interactionSource,
         useModifierBasedImplementation,
+        defaultMinSize,
         content,
     )
 }
@@ -182,6 +186,8 @@
  *
  * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
  * @sample com.android.systemui.compose.gallery.DialogLaunchScreen
+ * @param defaultMinSize true if a default minimum size should be enforced even if this Expandable
+ *   isn't currently clickable and false otherwise.
  */
 @Composable
 fun Expandable(
@@ -192,6 +198,7 @@
     // TODO(b/285250939): Default this to true then remove once the Compose QS expandables have
     // proven that the new implementation is robust.
     useModifierBasedImplementation: Boolean = false,
+    defaultMinSize: Boolean = true,
     content: @Composable (Expandable) -> Unit,
 ) {
     val controller = controller as ExpandableControllerImpl
@@ -209,7 +216,12 @@
 
     if (useModifierBasedImplementation) {
         Box(modifier.expandable(controller, onClick, interactionSource)) {
-            WrappedContent(controller.expandable, controller.contentColor, content)
+            WrappedContent(
+                controller.expandable,
+                controller.contentColor,
+                defaultMinSize = defaultMinSize,
+                content,
+            )
         }
         return
     }
@@ -221,7 +233,7 @@
     val wrappedContent =
         remember(content) {
             movableContentOf { expandable: Expandable ->
-                WrappedContent(expandable, contentColor, content)
+                WrappedContent(expandable, contentColor, defaultMinSize = defaultMinSize, content)
             }
         }
 
@@ -306,21 +318,24 @@
 private fun WrappedContent(
     expandable: Expandable,
     contentColor: Color,
+    defaultMinSize: Boolean,
     content: @Composable (Expandable) -> Unit,
 ) {
     val minSizeContent =
         @Composable {
-            // We make sure that the content itself (wrapped by the background) is at least 40.dp,
-            // which is the same as the M3 buttons. This applies even if onClick is null, to make it
-            // easier to write expandables that are sometimes clickable and sometimes not. There
-            // shouldn't be any Expandable smaller than 40dp because if the expandable is not
-            // clickable directly, then something in its content should be (and with a size >=
-            // 40dp).
-            val minSize = 40.dp
-            Box(
-                Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
-                contentAlignment = Alignment.Center,
-            ) {
+            if (defaultMinSize) {
+                // We make sure that the content itself (wrapped by the background) is at
+                // least 40.dp, which is the same as the M3 buttons. This applies even if
+                // onClick is null, to make it easier to write expandables that are
+                // sometimes clickable and sometimes not.
+                val minSize = 40.dp
+                Box(
+                    modifier = Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
+                    contentAlignment = Alignment.Center,
+                ) {
+                    content(expandable)
+                }
+            } else {
                 content(expandable)
             }
         }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedScrollController.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedScrollController.kt
index 2530a4f..54232e7 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedScrollController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedScrollController.kt
@@ -134,6 +134,7 @@
     private var bounds: NestedScrollableBound,
 ) : DelegatingNode(), NestedScrollConnection {
     private var childrenConsumedAnyScroll = false
+    private var availableOnPreScroll = Offset.Zero
 
     init {
         delegate(nestedScrollModifierNode(this, dispatcher = null))
@@ -153,12 +154,21 @@
         this.bounds = bounds
     }
 
+    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+        availableOnPreScroll = available
+        return Offset.Zero
+    }
+
     override fun onPostScroll(
         consumed: Offset,
         available: Offset,
         source: NestedScrollSource,
     ): Offset {
-        if (hasConsumedScrollInBounds(consumed.x) || hasConsumedScrollInBounds(consumed.y)) {
+        val consumedIncludingPreScroll = availableOnPreScroll - available
+        if (
+            hasConsumedScrollInBounds(consumedIncludingPreScroll.x) ||
+                hasConsumedScrollInBounds(consumedIncludingPreScroll.y)
+        ) {
             childrenConsumedAnyScroll = true
         }
 
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
index 84370ed..6fb3679 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt
@@ -43,7 +43,7 @@
     val context = LocalContext.current
 
     val colorScheme = remember(context, isDarkTheme) { platformColorScheme(isDarkTheme, context) }
-    val androidColorScheme = remember(context) { AndroidColorScheme(context) }
+    val androidColorScheme = remember(context, isDarkTheme) { AndroidColorScheme(context) }
     val typefaceNames = remember(context) { TypefaceNames.get(context) }
     val typefaceTokens = remember(typefaceNames) { TypefaceTokens(typefaceNames) }
     val typography =
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedScrollControllerTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedScrollControllerTest.kt
index 424af33..377cbe2 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedScrollControllerTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedScrollControllerTest.kt
@@ -23,9 +23,13 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onRoot
 import androidx.compose.ui.test.performTouchInput
@@ -103,4 +107,35 @@
         rule.waitForIdle()
         assertThat(state.isOuterScrollAllowed).isTrue()
     }
+
+    @Test
+    fun supportsPreScrolls() {
+        val state = NestedScrollControlState()
+        rule.setContent {
+            Box(
+                Modifier.fillMaxSize()
+                    .nestedScrollController(state)
+                    .nestedScroll(
+                        remember {
+                            object : NestedScrollConnection {
+                                override fun onPreScroll(
+                                    available: Offset,
+                                    source: NestedScrollSource,
+                                ): Offset = available
+                            }
+                        }
+                    )
+                    .scrollable(rememberScrollableState { 0f }, Orientation.Vertical)
+            )
+        }
+
+        rule.onRoot().performTouchInput {
+            down(topLeft)
+            moveBy(Offset(0f, bottom))
+        }
+        assertThat(state.isOuterScrollAllowed).isFalse()
+
+        rule.onRoot().performTouchInput { up() }
+        assertThat(state.isOuterScrollAllowed).isTrue()
+    }
 }
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 3150e94..2b8fe39 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
@@ -47,6 +47,7 @@
 import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.transitions
 import com.android.compose.modifiers.thenIf
+import com.android.systemui.Flags
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -104,7 +105,9 @@
             fade(Communal.Elements.Grid)
             fade(Communal.Elements.IndicationArea)
             fade(Communal.Elements.LockIcon)
-            fade(Communal.Elements.StatusBar)
+            if (!Flags.glanceableHubV2()) {
+                fade(Communal.Elements.StatusBar)
+            }
         }
         timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) }
     }
@@ -131,7 +134,9 @@
             fade(Communal.Elements.Grid)
             fade(Communal.Elements.IndicationArea)
             fade(Communal.Elements.LockIcon)
-            fade(Communal.Elements.StatusBar)
+            if (!Flags.glanceableHubV2()) {
+                fade(Communal.Elements.StatusBar)
+            }
         }
         timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 2d03e2b..0181928 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -29,6 +29,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.zIndex
 import com.android.compose.animation.scene.ContentScope
+import com.android.systemui.Flags
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
 import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
@@ -70,8 +71,10 @@
                 content = {
                     Box(modifier = Modifier.fillMaxSize()) {
                         with(communalPopupSection) { Popup() }
-                        with(ambientStatusBarSection) {
-                            AmbientStatusBar(modifier = Modifier.fillMaxWidth().zIndex(1f))
+                        if (!Flags.glanceableHubV2()) {
+                            with(ambientStatusBarSection) {
+                                AmbientStatusBar(modifier = Modifier.fillMaxWidth().zIndex(1f))
+                            }
                         }
                         CommunalHub(
                             viewModel = viewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 7782705..336f9e1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -38,6 +38,7 @@
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.shape.CornerSize
 import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.Icon
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
@@ -81,6 +82,7 @@
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.qs.flags.QsInCompose
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
@@ -388,6 +390,7 @@
 }
 
 /** A larger button with an icon, some text and an optional dot (to indicate new changes). */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun TextButton(
     icon: Icon,
@@ -422,10 +425,13 @@
             Text(
                 text,
                 Modifier.weight(1f),
-                style = MaterialTheme.typography.bodyMedium,
-                // TODO(b/242040009): Remove this letter spacing. We should only use the M3 text
-                // styles without modifying them.
-                letterSpacing = 0.01.em,
+                style =
+                    if (QsInCompose.isEnabled) {
+                        MaterialTheme.typography.labelLarge
+                    } else {
+                        MaterialTheme.typography.bodyMedium
+                    },
+                letterSpacing = if (QsInCompose.isEnabled) 0.em else 0.01.em,
                 color = colorAttr(R.attr.onShadeInactiveVariant),
                 maxLines = 1,
                 overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 4b3ebc2..da54cb8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -58,6 +58,7 @@
 import androidx.compose.ui.semantics.customActions
 import androidx.compose.ui.semantics.disabled
 import androidx.compose.ui.semantics.progressBarRangeInfo
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.setProgress
 import androidx.compose.ui.semantics.stateDescription
 import androidx.compose.ui.unit.dp
@@ -106,7 +107,10 @@
         return
     }
 
-    Column(modifier = modifier.animateContentSize(), verticalArrangement = Arrangement.Top) {
+    Column(
+        modifier = modifier.animateContentSize().semantics(true) {},
+        verticalArrangement = Arrangement.Top,
+    ) {
         Row(
             horizontalArrangement = Arrangement.spacedBy(12.dp),
             modifier = Modifier.fillMaxWidth().height(40.dp),
@@ -123,7 +127,7 @@
                 text = state.label,
                 style = MaterialTheme.typography.titleMedium,
                 color = MaterialTheme.colorScheme.onSurface,
-                modifier = Modifier.weight(1f),
+                modifier = Modifier.weight(1f).clearAndSetSemantics {},
             )
             button?.invoke()
         }
@@ -134,12 +138,11 @@
             onValueChanged = onValueChange,
             onValueChangeFinished = { onValueChangeFinished?.invoke() },
             isEnabled = state.isEnabled,
-            stepDistance = state.a11yStep,
+            stepDistance = state.step,
             accessibilityParams =
                 AccessibilityParams(
-                    label = state.label,
-                    disabledMessage = state.disabledMessage,
-                    currentStateDescription = state.a11yStateDescription,
+                    contentDescription = state.a11yContentDescription,
+                    stateDescription = state.a11yStateDescription,
                 ),
             haptics =
                 hapticsViewModelFactory?.let {
@@ -169,7 +172,7 @@
                         text = disabledMessage,
                         color = MaterialTheme.colorScheme.onSurfaceVariant,
                         style = MaterialTheme.typography.labelSmall,
-                        modifier = Modifier.basicMarquee(),
+                        modifier = Modifier.basicMarquee().clearAndSetSemantics {},
                     )
                 }
             }
@@ -229,7 +232,7 @@
                         }
 
                     val newValue =
-                        (value + targetDirection * state.a11yStep).coerceIn(
+                        (value + targetDirection * state.step).coerceIn(
                             state.valueRange.start,
                             state.valueRange.endInclusive,
                         )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 8591375..358635e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -80,17 +80,18 @@
                 testScope.backgroundScope,
                 UnconfinedTestDispatcher(),
             )
-        DisplayRepositoryImpl(
+        val displaysWithDecorRepository =
+            DisplaysWithDecorationsRepositoryImpl(
                 commandQueue,
                 windowManager,
                 testScope.backgroundScope,
                 displayRepositoryFromLib,
             )
-            .also {
-                verify(displayManager, never()).registerDisplayListener(any(), any())
-                // It needs to be called, just once, for the initial value.
-                verify(displayManager).getDisplays()
-            }
+        DisplayRepositoryImpl(displayRepositoryFromLib, displaysWithDecorRepository).also {
+            verify(displayManager, never()).registerDisplayListener(any(), any())
+            // It needs to be called, just once, for the initial value.
+            verify(displayManager).getDisplays()
+        }
     }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
index 6c7783a..bf49d92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
@@ -19,6 +19,7 @@
 import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.testScope
@@ -43,9 +44,10 @@
     private val fakeDisplayRepository = kosmos.displayRepository
     private val fakePerDisplayInstanceProviderWithTeardown =
         kosmos.fakePerDisplayInstanceProviderWithTeardown
+    private val lifecycleManager = kosmos.fakeDisplayInstanceLifecycleManager
 
     private val underTest: PerDisplayInstanceRepositoryImpl<TestPerDisplayInstance> =
-        kosmos.fakePerDisplayInstanceRepository
+        kosmos.createPerDisplayInstanceRepository(overrideLifecycleManager = null)
 
     @Before
     fun addDisplays() = runBlocking {
@@ -108,6 +110,43 @@
         verify(kosmos.dumpManager).registerNormalDumpable(anyString(), any())
     }
 
+    @Test
+    fun perDisplay_afterCustomLifecycleManagerRemovesDisplay_destroyInstanceInvoked() =
+        testScope.runTest {
+            val underTest =
+                kosmos.createPerDisplayInstanceRepository(
+                    overrideLifecycleManager = lifecycleManager
+                )
+            // Let's start with both
+            lifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY_ID, NON_DEFAULT_DISPLAY_ID)
+
+            val instance = underTest[NON_DEFAULT_DISPLAY_ID]
+
+            lifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY_ID)
+
+            // Now that the lifecycle manager says so, let's make sure it was destroyed
+            assertThat(fakePerDisplayInstanceProviderWithTeardown.destroyed)
+                .containsExactly(instance)
+        }
+
+    @Test
+    fun perDisplay_lifecycleManagerDoesNotContainIt_displayRepositoryDoes_returnsNull() =
+        testScope.runTest {
+            val underTest =
+                kosmos.createPerDisplayInstanceRepository(
+                    overrideLifecycleManager = lifecycleManager
+                )
+            // only default display, so getting for the non-default one should fail, despite the
+            // repository having both displays already
+            lifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY_ID)
+
+            assertThat(underTest[NON_DEFAULT_DISPLAY_ID]).isNull()
+
+            lifecycleManager.displayIds.value = setOf(DEFAULT_DISPLAY_ID, NON_DEFAULT_DISPLAY_ID)
+
+            assertThat(underTest[NON_DEFAULT_DISPLAY_ID]).isNotNull()
+        }
+
     private fun createDisplay(displayId: Int): Display =
         display(type = Display.TYPE_INTERNAL, id = displayId)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
index 0f63150..d9990ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
@@ -275,14 +275,14 @@
         setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */
                 "abc/.def", /* validateActivity */ true, /* enableSetting */true,
                 /* enableOnLockScreen */ true);
-        mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
+        mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0,
                 UserHandle.USER_CURRENT);
-        mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
+        mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0,
                 UserHandle.USER_CURRENT);
 
-        mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
+        mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 1,
                 UserHandle.USER_CURRENT);
-        mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
+        mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 1,
                 UserHandle.USER_CURRENT);
         // Once from setup + twice from this function
         verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged();
@@ -297,14 +297,14 @@
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
         assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
-        mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
+        mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0,
                 UserHandle.USER_CURRENT);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
         assertThat(mController.isAllowedOnLockScreen()).isTrue();
         assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
-        mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
+        mSecureSettings.putIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 1,
                 UserHandle.USER_CURRENT);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt
index 1d42424..8d50cdc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt
@@ -19,13 +19,17 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.FakeQSTile
 import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.shade.domain.interactor.disableDualShade
+import com.android.systemui.shade.domain.interactor.enableDualShade
 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
@@ -34,6 +38,7 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
+@EnableSceneContainer
 class DetailsViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private lateinit var underTest: DetailsViewModel
@@ -45,10 +50,12 @@
         underTest = kosmos.detailsViewModel
     }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
-    fun changeTileDetailsViewModel() =
+    fun changeTileDetailsViewModelWithDualShadeEnabled() =
         with(kosmos) {
             testScope.runTest {
+                kosmos.enableDualShade()
                 val specs = listOf(spec, specNoDetails)
                 tileSpecRepository.setTiles(0, specs)
                 runCurrent()
@@ -85,4 +92,36 @@
                 assertThat(underTest.onTileClicked(null)).isFalse()
             }
         }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun ignoreChangingTileDetailsViewModelWithDualShadeDisabled() =
+        with(kosmos) {
+            testScope.runTest {
+                kosmos.disableDualShade()
+                val specs = listOf(spec, specNoDetails)
+                tileSpecRepository.setTiles(0, specs)
+                runCurrent()
+
+                val tiles = currentTilesInteractor.currentTiles.value
+
+                assertThat(currentTilesInteractor.currentTilesSpecs.size).isEqualTo(2)
+                assertThat(tiles[1].spec).isEqualTo(specNoDetails)
+                (tiles[1].tile as FakeQSTile).hasDetailsViewModel = false
+
+                assertThat(underTest.activeTileDetails).isNull()
+
+                // Click on the tile who has the `spec`.
+                assertThat(underTest.onTileClicked(spec)).isFalse()
+                assertThat(underTest.activeTileDetails).isNull()
+
+                // Click on a tile who dose not have a valid spec.
+                assertThat(underTest.onTileClicked(null)).isFalse()
+                assertThat(underTest.activeTileDetails).isNull()
+
+                // Click on a tile who dose not have a detailed view.
+                assertThat(underTest.onTileClicked(specNoDetails)).isFalse()
+                assertThat(underTest.activeTileDetails).isNull()
+            }
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt
index a13b864..1018748 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt
@@ -24,6 +24,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -126,7 +127,7 @@
     @Test
     @EnableFlags(NotificationBundleUi.FLAG_NAME)
     fun isBubble() {
-        assertThat(underTest.isBubbleCapable).isFalse()
+        assertThat(underTest.isBubble).isFalse()
     }
 
     @Test
@@ -152,4 +153,10 @@
     fun canShowFullScreen() {
         assertThat(underTest.isFullScreenCapable()).isFalse()
     }
+
+    @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    fun getPeopleNotificationType() {
+        assertThat(underTest.getPeopleNotificationType()).isEqualTo(TYPE_NON_PERSON)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
index 1018ded..7449064 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.RankingBuilder
 import com.android.systemui.statusbar.notification.mockNotificationActivityStarter
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.entryAdapterFactory
 import com.android.systemui.statusbar.notification.row.mockNotificationActionClickManager
@@ -109,7 +110,7 @@
     @Test
     @EnableFlags(NotificationBundleUi.FLAG_NAME)
     fun getRow_adapter() {
-        val row = Mockito.mock(ExpandableNotificationRow::class.java)
+        val row = mock(ExpandableNotificationRow::class.java)
         val notification: Notification =
             Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
 
@@ -127,7 +128,7 @@
     @Test
     @EnableFlags(NotificationBundleUi.FLAG_NAME)
     fun isGroupRoot_adapter_groupSummary() {
-        val row = Mockito.mock(ExpandableNotificationRow::class.java)
+        val row = mock(ExpandableNotificationRow::class.java)
         val notification: Notification =
             Notification.Builder(mContext, "")
                 .setSmallIcon(R.drawable.ic_person)
@@ -174,7 +175,7 @@
     @Test
     @EnableFlags(NotificationBundleUi.FLAG_NAME)
     fun isClearable_adapter() {
-        val row = Mockito.mock(ExpandableNotificationRow::class.java)
+        val row = mock(ExpandableNotificationRow::class.java)
         val notification: Notification =
             Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
 
@@ -192,7 +193,7 @@
     @Test
     @EnableFlags(NotificationBundleUi.FLAG_NAME)
     fun getSummarization_adapter() {
-        val row = Mockito.mock(ExpandableNotificationRow::class.java)
+        val row = mock(ExpandableNotificationRow::class.java)
         val notification: Notification =
             Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
 
@@ -212,7 +213,7 @@
     @Test
     @EnableFlags(NotificationBundleUi.FLAG_NAME)
     fun getIcons_adapter() {
-        val row = Mockito.mock(ExpandableNotificationRow::class.java)
+        val row = mock(ExpandableNotificationRow::class.java)
         val notification: Notification =
             Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
 
@@ -257,7 +258,7 @@
     @Test
     @EnableFlags(NotificationBundleUi.FLAG_NAME)
     fun canDragAndDrop() {
-        val pi = Mockito.mock(PendingIntent::class.java)
+        val pi = mock(PendingIntent::class.java)
         Mockito.`when`(pi.isActivity).thenReturn(true)
         val notification: Notification =
             Notification.Builder(mContext, "")
@@ -283,7 +284,7 @@
         val entry = NotificationEntryBuilder().setNotification(notification).build()
 
         underTest = factory.create(entry) as NotificationEntryAdapter
-        assertThat(underTest.isBubbleCapable).isEqualTo(entry.isBubble)
+        assertThat(underTest.isBubble).isEqualTo(entry.isBubble)
     }
 
     @Test
@@ -335,11 +336,21 @@
 
     @Test
     @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    fun getPeopleNotificationType() {
+        val entry = kosmos.msgStyleBubbleableFullPerson
+
+        underTest = factory.create(entry) as NotificationEntryAdapter
+
+        assertThat(underTest.peopleNotificationType).isEqualTo(TYPE_FULL_PERSON)
+    }
+
+    @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
     fun canShowFullScreen() {
         val notification: Notification =
             Notification.Builder(mContext, "")
                 .setSmallIcon(R.drawable.ic_person)
-                .setFullScreenIntent(Mockito.mock(PendingIntent::class.java), true)
+                .setFullScreenIntent(mock(PendingIntent::class.java), true)
                 .build()
 
         val entry =
@@ -354,6 +365,22 @@
 
     @Test
     @EnableFlags(NotificationBundleUi.FLAG_NAME)
+    fun onDragSuccess() {
+        val notification: Notification =
+            Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .addAction(mock(Notification.Action::class.java))
+                .build()
+        val entry = NotificationEntryBuilder().setNotification(notification).build()
+
+        underTest = factory.create(entry) as NotificationEntryAdapter
+
+        underTest.onDragSuccess()
+        verify(kosmos.mockNotificationActivityStarter).onDragSuccess(entry)
+    }
+
+    @Test
+    @EnableFlags(NotificationBundleUi.FLAG_NAME)
     fun onNotificationBubbleIconClicked() {
         val notification: Notification =
             Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
@@ -372,7 +399,7 @@
         val notification: Notification =
             Notification.Builder(mContext, "")
                 .setSmallIcon(R.drawable.ic_person)
-                .addAction(Mockito.mock(Notification.Action::class.java))
+                .addAction(mock(Notification.Action::class.java))
                 .build()
 
         val entry = NotificationEntryBuilder().setNotification(notification).build()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt
index 7fa157f..ba2d40b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.kt
@@ -27,7 +27,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.runTest
@@ -35,10 +34,10 @@
 import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.core.StatusBarRootModernization
-import com.android.systemui.statusbar.notification.buildNotificationEntry
-import com.android.systemui.statusbar.notification.buildOngoingCallEntry
-import com.android.systemui.statusbar.notification.buildPromotedOngoingEntry
 import com.android.systemui.statusbar.notification.collection.buildEntry
+import com.android.systemui.statusbar.notification.collection.buildNotificationEntry
+import com.android.systemui.statusbar.notification.collection.buildOngoingCallEntry
+import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.notifPipeline
@@ -49,7 +48,6 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
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 893c179..4c099b3 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
@@ -455,6 +455,7 @@
 
         assertThat(content).isNotNull()
         assertThat(content?.style).isEqualTo(Style.Call)
+        assertThat(content?.title).isEqualTo(TEST_PERSON_NAME)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
index 6926677..6192399 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
@@ -28,9 +28,9 @@
 import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.core.StatusBarRootModernization
-import com.android.systemui.statusbar.notification.buildNotificationEntry
-import com.android.systemui.statusbar.notification.buildOngoingCallEntry
-import com.android.systemui.statusbar.notification.buildPromotedOngoingEntry
+import com.android.systemui.statusbar.notification.collection.buildNotificationEntry
+import com.android.systemui.statusbar.notification.collection.buildOngoingCallEntry
+import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry
 import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
 import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index d306a5b..0d45335 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI
 import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -318,7 +319,7 @@
         val notification = Notification.Builder(mContext).build()
         val sbn =
             SbnBuilder().setNotification(notification).setUser(UserHandle.of(USER_ALL)).build()
-        whenever(view.entry)
+        whenever(view.entryLegacy)
             .thenReturn(
                 NotificationEntryBuilder().setSbn(sbn).setUser(UserHandle.of(USER_ALL)).build()
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 9fdfca1..9536656 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.platform.test.annotations.DisableFlags;
 import android.provider.Settings;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -38,7 +39,10 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -54,6 +58,7 @@
 @SmallTest
 public class NotificationMenuRowTest extends LeakCheckedTest {
 
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
     private ExpandableNotificationRow mRow;
     private View mView;
     private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@@ -66,6 +71,8 @@
         mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class);
         NotificationEntry entry = new NotificationEntryBuilder().build();
         when(mRow.getEntry()).thenReturn(entry);
+        EntryAdapter entryAdapter = mKosmos.getEntryAdapterFactory().create(entry);
+        when(mRow.getEntryAdapter()).thenReturn(entryAdapter);
     }
 
     @Test
@@ -413,6 +420,7 @@
         assertTrue("when alpha is .5, menu is visible", row.isMenuVisible());
     }
 
+    @DisableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES)
     @Test
     public void testOnTouchMove() {
         NotificationMenuRow row = Mockito.spy(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
index ccc8be7..6c6ba93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
@@ -130,7 +130,9 @@
         kosmos.testScope.runTest {
             // GIVEN a threshold of 100 px
             val threshold = 100f
-            underTest.setSwipeThresholdPx(threshold)
+            underTest.onDensityChange(
+                threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+            )
 
             // GIVEN that targets are set and the rows are being pulled
             setTargets()
@@ -150,7 +152,9 @@
         kosmos.testScope.runTest {
             // GIVEN a threshold of 100 px
             val threshold = 100f
-            underTest.setSwipeThresholdPx(threshold)
+            underTest.onDensityChange(
+                threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+            )
 
             // GIVEN that targets are set and the rows are being pulled
             canRowBeDismissed = false
@@ -172,7 +176,9 @@
         kosmos.testScope.runTest {
             // GIVEN a threshold of 100 px
             val threshold = 100f
-            underTest.setSwipeThresholdPx(threshold)
+            underTest.onDensityChange(
+                threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+            )
 
             // GIVEN that targets are set and the rows are being pulled
             setTargets()
@@ -192,7 +198,9 @@
         kosmos.testScope.runTest {
             // GIVEN a threshold of 100 px
             val threshold = 100f
-            underTest.setSwipeThresholdPx(threshold)
+            underTest.onDensityChange(
+                threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+            )
 
             // GIVEN that targets are set and the rows are being pulled
             canRowBeDismissed = false
@@ -294,6 +302,29 @@
             assertThat(underTest.isSwipedViewRoundableSet).isFalse()
         }
 
+    @Test
+    fun isMagneticRowDismissible_isDismissibleWhenDetached() =
+        kosmos.testScope.runTest {
+            setDetachedState()
+
+            val isDismissible = underTest.isMagneticRowSwipeDetached(swipedRow)
+            assertThat(isDismissible).isTrue()
+        }
+
+    @Test
+    fun setMagneticRowTranslation_whenDetached_belowAttachThreshold_reattaches() =
+        kosmos.testScope.runTest {
+            // GIVEN that the swiped view has been detached
+            setDetachedState()
+
+            // WHEN setting a new translation above the attach threshold
+            val translation = 50f
+            underTest.setMagneticRowTranslation(swipedRow, translation)
+
+            // THEN the swiped view reattaches magnetically and the state becomes PULLING
+            assertThat(underTest.currentState).isEqualTo(State.PULLING)
+        }
+
     @After
     fun tearDown() {
         // We reset the manager so that all MagneticRowListener can cancel all animations
@@ -302,7 +333,9 @@
 
     private fun setDetachedState() {
         val threshold = 100f
-        underTest.setSwipeThresholdPx(threshold)
+        underTest.onDensityChange(
+            threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+        )
 
         // Set the pulling state
         setTargets()
@@ -327,8 +360,8 @@
     private fun MagneticRowListener.asTestableListener(rowIndex: Int): MagneticRowListener {
         val delegate = this
         return object : MagneticRowListener {
-            override fun setMagneticTranslation(translation: Float) {
-                delegate.setMagneticTranslation(translation)
+            override fun setMagneticTranslation(translation: Float, trackEagerly: Boolean) {
+                delegate.setMagneticTranslation(translation, trackEagerly)
             }
 
             override fun triggerMagneticForce(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 789701f5..de48f40 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -49,6 +49,7 @@
 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.classifier.FalsingManagerFake;
 import com.android.systemui.flags.FakeFeatureFlags;
@@ -362,6 +363,7 @@
         verify(mSwipeHelper, times(1)).isFalseGesture();
     }
 
+    @DisableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES)
     @Test
     public void testIsDismissGesture_farEnough() {
         doReturn(false).when(mSwipeHelper).isFalseGesture();
@@ -374,6 +376,20 @@
         verify(mSwipeHelper, times(1)).isFalseGesture();
     }
 
+    @EnableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES)
+    @Test
+    public void testIsDismissGesture_magneticSwipeIsDismissible() {
+        doReturn(false).when(mSwipeHelper).isFalseGesture();
+        doReturn(false).when(mSwipeHelper).swipedFarEnough();
+        doReturn(false).when(mSwipeHelper).swipedFastEnough();
+        doReturn(true).when(mCallback).isMagneticViewDetached(any());
+        when(mCallback.canChildBeDismissedInDirection(any(), anyBoolean())).thenReturn(true);
+        when(mEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_UP);
+
+        assertTrue("Should be a dismissal", mSwipeHelper.isDismissGesture(mEvent));
+        verify(mSwipeHelper, times(1)).isFalseGesture();
+    }
+
     @Test
     public void testIsDismissGesture_notFarOrFastEnough() {
         doReturn(false).when(mSwipeHelper).isFalseGesture();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 41cca19..6ec1f91 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -129,7 +129,7 @@
         whenever(notificationShelf.viewState).thenReturn(ExpandableViewState())
         whenever(notificationRow.key).thenReturn("key")
         whenever(notificationRow.viewState).thenReturn(ExpandableViewState())
-        whenever(notificationRow.entry).thenReturn(notificationEntry)
+        whenever(notificationRow.entryLegacy).thenReturn(notificationEntry)
         whenever(notificationRow.entryAdapter).thenReturn(notificationEntryAdapter)
         whenever(notificationRow.roundableState)
             .thenReturn(RoundableState(notificationRow, notificationRow, 0f))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
index 46430af..1f37291 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -790,6 +790,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
     fun animateToGlanceableHub_affectsAlpha() =
         testScope.runTest {
             try {
@@ -809,6 +810,7 @@
         }
 
     @Test
+    @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
     fun animateToGlanceableHub_alphaResetOnCommunalNotShowing() =
         testScope.runTest {
             try {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index c23e0e7..1cc2911 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -221,6 +221,7 @@
 
         when(enr.getPrivateLayout()).thenReturn(privateLayout);
         when(enr.getEntry()).thenReturn(enrEntry);
+        when(enr.getEntryLegacy()).thenReturn(enrEntry);
         when(enr.getEntryAdapter()).thenReturn(enrEntryAdapter);
         when(enr.isChildInGroup()).thenReturn(true);
         when(enr.areChildrenExpanded()).thenReturn(false);
@@ -251,6 +252,7 @@
 
         when(enr.getPrivateLayout()).thenReturn(privateLayout);
         when(enr.getEntry()).thenReturn(enrEntry);
+        when(enr.getEntryLegacy()).thenReturn(enrEntry);
         when(enr.isChildInGroup()).thenReturn(true);
         when(enr.areChildrenExpanded()).thenReturn(true);
 
@@ -277,6 +279,7 @@
 
         when(enr.getPrivateLayout()).thenReturn(privateLayout);
         when(enr.getEntry()).thenReturn(enrEntry);
+        when(enr.getEntryLegacy()).thenReturn(enrEntry);
         when(enr.isChildInGroup()).thenReturn(false);
         when(enr.isPinned()).thenReturn(false);
         when(enr.isExpanded()).thenReturn(false);
@@ -305,6 +308,7 @@
 
         when(enr.getPrivateLayout()).thenReturn(privateLayout);
         when(enr.getEntry()).thenReturn(enrEntry);
+        when(enr.getEntryLegacy()).thenReturn(enrEntry);
         when(enr.isChildInGroup()).thenReturn(false);
         when(enr.isPinned()).thenReturn(false);
         when(enr.isExpanded()).thenReturn(true);
@@ -333,6 +337,7 @@
 
         when(enr.getPrivateLayout()).thenReturn(privateLayout);
         when(enr.getEntry()).thenReturn(enrEntry);
+        when(enr.getEntryLegacy()).thenReturn(enrEntry);
         when(enr.isChildInGroup()).thenReturn(false);
         when(enr.isPinned()).thenReturn(true);
         when(enr.isPinnedAndExpanded()).thenReturn(false);
@@ -361,6 +366,7 @@
 
         when(enr.getPrivateLayout()).thenReturn(privateLayout);
         when(enr.getEntry()).thenReturn(enrEntry);
+        when(enr.getEntryLegacy()).thenReturn(enrEntry);
         when(enr.isChildInGroup()).thenReturn(false);
         when(enr.isPinned()).thenReturn(true);
         when(enr.isPinnedAndExpanded()).thenReturn(true);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index bafa8cf..da5622a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,8 +17,10 @@
 
 package com.android.systemui.user.data.repository
 
+import android.app.admin.DevicePolicyManager
 import android.app.admin.devicePolicyManager
 import android.content.Intent
+import android.content.applicationContext
 import android.content.pm.UserInfo
 import android.internal.statusbar.fakeStatusBarService
 import android.os.UserHandle
@@ -77,10 +79,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         tracker = FakeUserTracker()
-        context.orCreateTestableResources.addOverride(
-            R.bool.config_userSwitchingMustGoThroughLoginScreen,
-            false,
-        )
+        setUserSwitchingMustGoThroughLoginScreen(false)
     }
 
     @Test
@@ -308,6 +307,117 @@
             job.cancel()
         }
 
+    @Test
+    fun isSecondaryUserLogoutEnabled_secondaryLogoutDisabled_alwaysFalse() =
+        testScope.runTest {
+            underTest = create(testScope.backgroundScope)
+            mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT)
+            setSecondaryUserLogoutEnabled(false)
+            setUpUsers(count = 2, selectedIndex = 0)
+            tracker.onProfileChanged()
+
+            val secondaryUserLogoutEnabled by
+                collectLastValue(underTest.isSecondaryUserLogoutEnabled)
+
+            assertThat(secondaryUserLogoutEnabled).isFalse()
+
+            setUpUsers(count = 2, selectedIndex = 1)
+            tracker.onProfileChanged()
+            assertThat(secondaryUserLogoutEnabled).isFalse()
+        }
+
+    @Test
+    fun isSecondaryUserLogoutEnabled_secondaryLogoutEnabled_NullLogoutUser_alwaysFalse() =
+        testScope.runTest {
+            underTest = create(testScope.backgroundScope)
+            mockLogoutUser(LogoutUserResult.NONE)
+            setSecondaryUserLogoutEnabled(true)
+            setUpUsers(count = 2, selectedIndex = 0)
+            tracker.onProfileChanged()
+
+            val secondaryUserLogoutEnabled by
+                collectLastValue(underTest.isSecondaryUserLogoutEnabled)
+
+            assertThat(secondaryUserLogoutEnabled).isFalse()
+
+            setUpUsers(count = 2, selectedIndex = 1)
+            tracker.onProfileChanged()
+            assertThat(secondaryUserLogoutEnabled).isFalse()
+        }
+
+    @Test
+    fun isSecondaryUserLogoutEnabled_secondaryLogoutEnabled_NonSystemLogoutUser_trueWhenNonSystem() =
+        testScope.runTest {
+            underTest = create(testScope.backgroundScope)
+            mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT)
+            setSecondaryUserLogoutEnabled(true)
+            setUpUsers(count = 2, selectedIndex = 0)
+            tracker.onProfileChanged()
+
+            val secondaryUserLogoutEnabled by
+                collectLastValue(underTest.isSecondaryUserLogoutEnabled)
+
+            assertThat(secondaryUserLogoutEnabled).isFalse()
+
+            setUpUsers(count = 2, selectedIndex = 1)
+            tracker.onProfileChanged()
+            assertThat(secondaryUserLogoutEnabled).isTrue()
+        }
+
+    @Test
+    fun isLogoutToSystemUserEnabled_logoutThroughLoginScreenDisabled_alwaysFalse() =
+        testScope.runTest {
+            underTest = create(testScope.backgroundScope)
+            mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT)
+            setUserSwitchingMustGoThroughLoginScreen(false)
+            setUpUsers(count = 2, selectedIndex = 0)
+            tracker.onProfileChanged()
+
+            val logoutToSystemUserEnabled by collectLastValue(underTest.isLogoutToSystemUserEnabled)
+
+            assertThat(logoutToSystemUserEnabled).isFalse()
+
+            setUpUsers(count = 2, selectedIndex = 1)
+            tracker.onProfileChanged()
+            assertThat(logoutToSystemUserEnabled).isFalse()
+        }
+
+    @Test
+    fun isLogoutToSystemUserEnabled_logoutThroughLoginScreenEnabled_NullLogoutUser_alwaysFalse() =
+        testScope.runTest {
+            underTest = create(testScope.backgroundScope)
+            mockLogoutUser(LogoutUserResult.NONE)
+            setUserSwitchingMustGoThroughLoginScreen(true)
+            setUpUsers(count = 2, selectedIndex = 0)
+            tracker.onProfileChanged()
+
+            val logoutToSystemUserEnabled by collectLastValue(underTest.isLogoutToSystemUserEnabled)
+
+            assertThat(logoutToSystemUserEnabled).isFalse()
+
+            setUpUsers(count = 2, selectedIndex = 1)
+            tracker.onProfileChanged()
+            assertThat(logoutToSystemUserEnabled).isFalse()
+        }
+
+    @Test
+    fun isLogoutToSystemUserEnabled_logoutThroughLoginScreenEnabled_NonSystemLogoutUser_trueWhenNonSystem() =
+        testScope.runTest {
+            underTest = create(testScope.backgroundScope)
+            mockLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT)
+            setUserSwitchingMustGoThroughLoginScreen(true)
+            setUpUsers(count = 2, selectedIndex = 0)
+            tracker.onProfileChanged()
+
+            val logoutToSystemUserEnabled by collectLastValue(underTest.isLogoutToSystemUserEnabled)
+
+            assertThat(logoutToSystemUserEnabled).isFalse()
+
+            setUpUsers(count = 2, selectedIndex = 1)
+            tracker.onProfileChanged()
+            assertThat(logoutToSystemUserEnabled).isTrue()
+        }
+
     private fun createUserInfo(id: Int, isGuest: Boolean): UserInfo {
         val flags = 0
         return UserInfo(
@@ -354,6 +464,38 @@
         assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
     }
 
+    private fun setSecondaryUserLogoutEnabled(enabled: Boolean) {
+        whenever(devicePolicyManager.isLogoutEnabled).thenReturn(enabled)
+        broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            kosmos.applicationContext,
+            Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+        )
+    }
+
+    private fun setUserSwitchingMustGoThroughLoginScreen(enabled: Boolean) {
+        context.orCreateTestableResources.addOverride(
+            R.bool.config_userSwitchingMustGoThroughLoginScreen,
+            enabled,
+        )
+    }
+
+    private fun mockLogoutUser(result: LogoutUserResult) {
+        when (result) {
+            LogoutUserResult.NONE -> {
+                whenever(devicePolicyManager.logoutUser).thenReturn(null)
+            }
+            LogoutUserResult.NON_SYSTEM_CURRENT -> {
+                whenever(devicePolicyManager.logoutUser).thenAnswer {
+                    if (tracker.userHandle != UserHandle.SYSTEM) {
+                        tracker.userHandle
+                    } else {
+                        null
+                    }
+                }
+            }
+        }
+    }
+
     private fun create(scope: CoroutineScope): UserRepositoryImpl {
         return UserRepositoryImpl(
             appContext = context,
@@ -373,4 +515,9 @@
     companion object {
         @JvmStatic private val IMMEDIATE = Dispatchers.Main.immediate
     }
+
+    private enum class LogoutUserResult {
+        NONE,
+        NON_SYSTEM_CURRENT,
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt
index 04ab988..b1a3caf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
 
 import android.bluetooth.BluetoothDevice
+import android.graphics.drawable.TestStubDrawable
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -63,6 +64,12 @@
 
             assertThat(audioSharingSlider!!.label).isEqualTo("my headset 2")
             assertThat(audioSharingSlider!!.icon)
-                .isEqualTo(Icon.Resource(R.drawable.ic_volume_media_bt, null))
+                .isEqualTo(
+                    Icon.Loaded(
+                        drawable = TestStubDrawable(),
+                        res = R.drawable.ic_volume_media_bt,
+                        contentDescription = null,
+                    )
+                )
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
index 9e8cde3..ffe8e92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.Flags
 import android.app.NotificationManager.INTERRUPTION_FILTER_NONE
+import android.graphics.drawable.TestStubDrawable
 import android.media.AudioManager
 import android.platform.test.annotations.EnableFlags
 import android.service.notification.ZenPolicy
@@ -28,7 +29,6 @@
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.collectLastValue
@@ -39,8 +39,6 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.volume.data.repository.audioSharingRepository
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.mock
@@ -173,6 +171,12 @@
 
             assertThat(mediaSlider!!.label).isEqualTo("my headset 1")
             assertThat(mediaSlider!!.icon)
-                .isEqualTo(Icon.Resource(R.drawable.ic_volume_media_bt, null))
+                .isEqualTo(
+                    Icon.Loaded(
+                        drawable = TestStubDrawable(),
+                        res = R.drawable.ic_volume_media_bt,
+                        contentDescription = null,
+                    )
+                )
         }
 }
diff --git a/packages/SystemUI/res/color/brightness_slider_track.xml b/packages/SystemUI/res/color/brightness_slider_track.xml
new file mode 100644
index 0000000..c6e9b65
--- /dev/null
+++ b/packages/SystemUI/res/color/brightness_slider_track.xml
@@ -0,0 +1,19 @@
+<?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:color="@android:color/system_neutral2_500" android:lStar="40" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
index 88d3ecb..d38da7b 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
@@ -25,7 +25,7 @@
             <shape>
                 <size android:height="@dimen/rounded_slider_track_width" />
                 <corners android:radius="@dimen/rounded_slider_track_corner_radius" />
-                <solid android:color="@androidprv:color/customColorShadeInactive" />
+                <solid android:color="@color/brightness_slider_track" />
             </shape>
         </inset>
     </item>
diff --git a/packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_background.xml b/packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_background.xml
new file mode 100644
index 0000000..2173641
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing_device_ambient_expand_icon_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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/hearing_device_ambient_icon_background"
+    android:insetTop="10dp"
+    android:insetBottom="10dp"/>
diff --git a/packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.xml b/packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.xml
new file mode 100644
index 0000000..81ef3d7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing_device_ambient_icon_background.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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <solid android:color="@androidprv:color/materialColorSurfaceContainerHighest" />
+    <corners
+        android:bottomLeftRadius="?android:attr/dialogCornerRadius"
+        android:topLeftRadius="?android:attr/dialogCornerRadius"
+        android:bottomRightRadius="?android:attr/dialogCornerRadius"
+        android:topRightRadius="?android:attr/dialogCornerRadius" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml b/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml
index 73704f8..f17cc96 100644
--- a/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml
+++ b/packages/SystemUI/res/drawable/rear_display_dialog_seekbar.xml
@@ -21,7 +21,7 @@
         android:height="2dp"
         android:width="@dimen/rear_display_progress_width">
         <shape android:shape="rectangle">
-            <solid android:color="@androidprv:color/materialColorSurfaceContainer" />
+            <solid android:color="?android:attr/colorAccent"/>
         </shape>
     </item>
     <item
@@ -29,4 +29,4 @@
         android:gravity="center_vertical|fill_horizontal">
         <com.android.systemui.util.RoundedCornerProgressDrawable android:drawable="@drawable/rear_display_dialog_seekbar_progress" />
     </item>
-</layer-list>
\ No newline at end of file
+</layer-list>
diff --git a/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml b/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml
index fd409a5..d3e9db1 100644
--- a/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml
+++ b/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml
@@ -36,7 +36,8 @@
             android:padding="12dp"
             android:contentDescription="@string/hearing_devices_ambient_unmute"
             android:src="@drawable/ic_ambient_volume"
-            android:tint="@androidprv:color/materialColorOnSurface" />
+            android:tint="@androidprv:color/materialColorOnSurface"
+            android:background="@drawable/hearing_device_ambient_icon_background"/>
         <TextView
             android:id="@+id/ambient_title"
             android:layout_width="0dp"
@@ -56,7 +57,8 @@
             android:padding="10dp"
             android:contentDescription="@string/hearing_devices_ambient_expand_controls"
             android:src="@drawable/ic_hearing_device_expand"
-            android:tint="@androidprv:color/materialColorOnSurface" />
+            android:tint="@androidprv:color/materialColorOnSurface"
+            android:background="@drawable/hearing_device_ambient_expand_icon_background"/>
     </LinearLayout>
     <LinearLayout
         android:id="@+id/ambient_control_container"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 15519ff..7c6a1b1 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -146,7 +146,8 @@
     <color name="smart_reply_button_stroke">@*android:color/accent_device_default</color>
 
     <!-- Magic Action colors -->
-    <color name="magic_action_button_text_color">@androidprv:color/materialColorOnSurfaceVariant</color>
+    <color name="magic_action_button_text_color">@androidprv:color/materialColorOnSurface</color>
+    <color name="magic_action_button_stroke_color">@androidprv:color/materialColorOnSurface</color>
 
     <!-- Biometric dialog colors -->
     <color name="biometric_dialog_gray">#ff757575</color>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3fdb98b..b627bdf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1483,6 +1483,8 @@
     <string name="media_projection_entry_app_permission_dialog_continue_entire_screen">Share screen</string>
     <!-- 1P/3P apps disabled the single app projection option. [CHAR LIMIT=NONE] -->
     <string name="media_projection_entry_app_permission_dialog_single_app_disabled"><xliff:g id="app_name" example="Meet">%1$s</xliff:g> has disabled this option</string>
+    <!-- Explanation that the app requesting the projection does not support single app sharing[CHAR LIMIT=70] -->
+    <string name="media_projection_entry_app_permission_dialog_single_app_not_supported">Not supported by the app</string>
     <!-- Title of the activity that allows users to select an app to share to a 1P/3P app [CHAR LIMIT=70] -->
     <string name="media_projection_entry_share_app_selector_title">Choose app to share</string>
 
@@ -2558,6 +2560,9 @@
     <!-- Button to edit the tile ordering of quick settings [CHAR LIMIT=60] -->
     <string name="qs_edit">Edit</string>
 
+    <!-- Title for QS Edit mode screen [CHAR LIMIT=30] -->
+    <string name="qs_edit_tiles">Edit tiles</string>
+
     <!-- SysUI Tuner: Options for how clock is displayed [CHAR LIMIT=NONE] -->
     <string name="tuner_time">Time</string>
 
diff --git a/packages/SystemUI/shared/res/values/bools.xml b/packages/SystemUI/shared/res/values/bools.xml
index 98e5cea..a7ad48d 100644
--- a/packages/SystemUI/shared/res/values/bools.xml
+++ b/packages/SystemUI/shared/res/values/bools.xml
@@ -22,7 +22,4 @@
 <resources>
     <!-- Whether to add padding at the bottom of the complication clock -->
     <bool name="dream_overlay_complication_clock_bottom_padding">false</bool>
-
-    <!-- Whether to mark tasks that are present in the UI as perceptible tasks. -->
-    <bool name="config_usePerceptibleTasks">false</bool>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 487d1ce..b981f98 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -46,8 +46,6 @@
 import android.window.TaskSnapshot;
 
 import com.android.internal.app.IVoiceInteractionManagerService;
-import com.android.server.am.Flags;
-import com.android.systemui.shared.R;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -324,14 +322,6 @@
     }
 
     /**
-     * Returns true if tasks with a presence in the UI should be marked as perceptible tasks.
-     */
-    public static boolean usePerceptibleTasks(Context context) {
-        return Flags.perceptibleTasks()
-                && context.getResources().getBoolean(R.bool.config_usePerceptibleTasks);
-    }
-
-    /**
      * Returns true if the running task represents the home task
      */
     public static boolean isHomeTask(RunningTaskInfo info) {
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 0894667..d017754 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -778,18 +778,26 @@
 
     protected boolean swipedFarEnough() {
         float translation = getTranslation(mTouchedView);
-        return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(
-                mTouchedView);
+        return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(mTouchedView);
     }
 
     public boolean isDismissGesture(MotionEvent ev) {
         float translation = getTranslation(mTouchedView);
         return ev.getActionMasked() == MotionEvent.ACTION_UP
                 && !mFalsingManager.isUnlockingDisabled()
-                && !isFalseGesture() && (swipedFastEnough() || swipedFarEnough())
+                && !isFalseGesture() && isSwipeDismissible()
                 && mCallback.canChildBeDismissedInDirection(mTouchedView, translation > 0);
     }
 
+    /** Can the swipe gesture on the touched view be considered as a dismiss intention */
+    public boolean isSwipeDismissible() {
+        if (magneticNotificationSwipes()) {
+            return mCallback.isMagneticViewDetached(mTouchedView) || swipedFastEnough();
+        } else {
+            return swipedFastEnough() || swipedFarEnough();
+        }
+    }
+
     /** Returns true if the gesture should be rejected. */
     public boolean isFalseGesture() {
         boolean falsingDetected = mCallback.isAntiFalsingNeeded();
@@ -970,6 +978,13 @@
         void onMagneticInteractionEnd(View view, float velocity);
 
         /**
+         * Determine if a view managed by magnetic interactions is magnetically detached
+         * @param view The magnetic view
+         * @return if the view is detached according to its magnetic state.
+         */
+        boolean isMagneticViewDetached(View view);
+
+        /**
          * Called when the child is long pressed and available to start drag and drop.
          *
          * @param v the view that was long pressed.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index bd3dfe0..8025d4b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -317,14 +317,14 @@
     public static float avoidVerticalDisplayCutout(
             float y, float menuHeight, Rect bounds, Rect cutout) {
         if (cutout.top > y + menuHeight || cutout.bottom < y) {
-            return y;
+            return clampVerticalPosition(y, menuHeight, bounds.top, bounds.bottom);
         }
 
         boolean topAvailable = cutout.top - bounds.top >= menuHeight;
         boolean bottomAvailable = bounds.bottom - cutout.bottom >= menuHeight;
         boolean topOrBottom;
         if (!topAvailable && !bottomAvailable) {
-            return y;
+            return clampVerticalPosition(y, menuHeight, bounds.top, bounds.bottom);
         } else if (topAvailable && !bottomAvailable) {
             topOrBottom = true;
         } else if (!topAvailable && bottomAvailable) {
@@ -332,7 +332,16 @@
         } else {
             topOrBottom = y + menuHeight * 0.5f < cutout.centerY();
         }
-        return (topOrBottom) ? cutout.top - menuHeight : cutout.bottom;
+
+        float finalPosition = (topOrBottom) ? cutout.top - menuHeight : cutout.bottom;
+        return clampVerticalPosition(finalPosition, menuHeight, bounds.top, bounds.bottom);
+    }
+
+    private static float clampVerticalPosition(
+            float position, float height, float min, float max) {
+        position = Float.max(min + height / 2, position);
+        position = Float.min(max - height / 2, position);
+        return position;
     }
 
     boolean isMenuOnLeftSide() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 14b13d1..24b9551 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -286,7 +286,9 @@
                             mLaunchSourceId);
                     final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS)
                             .putExtra(Intent.EXTRA_COMPONENT_NAME,
-                                    ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
+                                    ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString())
+                            .setPackage(mQSSettingsPackageRepository.getSettingsPackageName());
+
                     mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
                             mDialogTransitionAnimator.createActivityTransitionController(
                                     dialog));
@@ -588,9 +590,7 @@
                     com.android.internal.R.color.materialColorOnPrimaryContainer));
         }
         text.setText(item.getToolName());
-        Intent intent = item.getToolIntent()
-                .setPackage(mQSSettingsPackageRepository.getSettingsPackageName());
-
+        Intent intent = item.getToolIntent();
         view.setOnClickListener(v -> {
             final String name = intent.getComponent() != null
                     ? intent.getComponent().flattenToString()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 22d2aaf..87e9784 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -32,7 +32,6 @@
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.settingslib.Utils
 import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.lightRevealMigration
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.dagger.SysUISingleton
@@ -43,6 +42,7 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
+import com.android.systemui.shared.Flags.ambientAod
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LiftReveal
 import com.android.systemui.statusbar.LightRevealEffect
@@ -196,7 +196,7 @@
 
         // This code path is not used if the KeyguardTransitionRepository is managing the light
         // reveal scrim.
-        if (!lightRevealMigration()) {
+        if (!ambientAod()) {
             if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
                 circleReveal?.let {
                     lightRevealScrim.revealAmount = 0f
@@ -213,7 +213,7 @@
     }
 
     override fun onKeyguardFadingAwayChanged() {
-        if (lightRevealMigration()) {
+        if (ambientAod()) {
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt
index 9db7b50..1301fb8 100644
--- a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.common.domain.interactor
 
 import android.util.Log
+import com.android.app.displaylib.PerDisplayRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.display.data.repository.DisplayRepository
-import com.android.systemui.display.data.repository.PerDisplayRepository
 import com.android.systemui.model.StateChange
 import com.android.systemui.model.SysUiState
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
index 2adaec2..6792f31 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
@@ -19,6 +19,7 @@
 import android.annotation.DrawableRes
 import android.graphics.drawable.Drawable
 import androidx.compose.runtime.Stable
+import com.android.systemui.common.shared.model.Icon.Loaded
 
 /**
  * Models an icon, that can either be already [loaded][Icon.Loaded] or be a [reference]
@@ -33,8 +34,37 @@
     constructor(
         val drawable: Drawable,
         override val contentDescription: ContentDescription?,
+        /**
+         * Serves as an id to compare two instances. When provided this is used alongside
+         * [contentDescription] to determine equality. This is useful when comparing icons
+         * representing the same UI, but with different [drawable] instances.
+         */
         @DrawableRes val res: Int? = null,
-    ) : Icon()
+    ) : Icon() {
+
+        override fun equals(other: Any?): Boolean {
+            val that = other as? Loaded ?: return false
+
+            if (this.res != null && that.res != null) {
+                return this.res == that.res && this.contentDescription == that.contentDescription
+            }
+
+            return this.res == that.res &&
+                this.drawable == that.drawable &&
+                this.contentDescription == that.contentDescription
+        }
+
+        override fun hashCode(): Int {
+            var result = contentDescription?.hashCode() ?: 0
+            result =
+                if (res != null) {
+                    31 * result + res.hashCode()
+                } else {
+                    31 * result + drawable.hashCode()
+                }
+            return result
+        }
+    }
 
     data class Resource(
         @DrawableRes val res: Int,
@@ -49,4 +79,4 @@
 fun Drawable.asIcon(
     contentDescription: ContentDescription? = null,
     @DrawableRes res: Int? = null,
-): Icon.Loaded = Icon.Loaded(this, contentDescription, res)
+): Loaded = Loaded(this, contentDescription, res)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 756edb3..5a4b0b0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -229,7 +229,7 @@
             MutableStateFlow(false)
         }
 
-    val blurRadiusPx: Float = blurConfig.maxBlurRadiusPx / 2.0f
+    val blurRadiusPx: Float = blurConfig.maxBlurRadiusPx
 
     init {
         // Initialize our media host for the UMO. This only needs to happen once and must be done
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt
index 39708a7..3520439 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.dagger
 
-import com.android.systemui.display.data.repository.DefaultDisplayOnlyInstanceRepositoryImpl
-import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl
-import com.android.systemui.display.data.repository.PerDisplayRepository
+import com.android.app.displaylib.DefaultDisplayOnlyInstanceRepositoryImpl
+import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl
+import com.android.app.displaylib.PerDisplayRepository
 import com.android.systemui.model.SysUIStateInstanceProvider
 import com.android.systemui.model.SysUiState
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index c201fbf..edee64e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -65,7 +65,7 @@
 import com.android.systemui.demomode.dagger.DemoModeModule;
 import com.android.systemui.deviceentry.DeviceEntryModule;
 import com.android.systemui.display.DisplayModule;
-import com.android.systemui.display.data.repository.PerDisplayRepository;
+import com.android.app.displaylib.PerDisplayRepository;
 import com.android.systemui.doze.dagger.DozeComponent;
 import com.android.systemui.dreams.dagger.DreamModule;
 import com.android.systemui.flags.FeatureFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index f331695..02d9c66 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -18,7 +18,9 @@
 
 import android.hardware.display.DisplayManager
 import android.os.Handler
+import com.android.app.displaylib.DisplayLibBackground
 import com.android.app.displaylib.DisplayLibComponent
+import com.android.app.displaylib.PerDisplayRepository
 import com.android.app.displaylib.createDisplayLibComponent
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
@@ -31,10 +33,11 @@
 import com.android.systemui.display.data.repository.DisplayScopeRepositoryImpl
 import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
 import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepositoryImpl
+import com.android.systemui.display.data.repository.DisplaysWithDecorationsRepository
+import com.android.systemui.display.data.repository.DisplaysWithDecorationsRepositoryImpl
 import com.android.systemui.display.data.repository.FocusedDisplayRepository
 import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
 import com.android.systemui.display.data.repository.PerDisplayRepoDumpHelper
-import com.android.systemui.display.data.repository.PerDisplayRepository
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
 import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractorModule
@@ -83,8 +86,17 @@
     ): DisplayWindowPropertiesRepository
 
     @Binds
+    fun displaysWithDecorationsRepository(
+        impl: DisplaysWithDecorationsRepositoryImpl
+    ): DisplaysWithDecorationsRepository
+
+    @Binds
     fun dumpRegistrationLambda(helper: PerDisplayRepoDumpHelper): PerDisplayRepository.InitCallback
 
+    @Binds
+    @DisplayLibBackground
+    fun bindDisplayLibBackground(@Background bgScope: CoroutineScope): CoroutineScope
+
     companion object {
         @Provides
         @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 051fe7e..01bbf2d 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -16,95 +16,25 @@
 
 package com.android.systemui.display.data.repository
 
-import android.annotation.SuppressLint
-import android.view.IWindowManager
 import com.android.app.displaylib.DisplayRepository as DisplayRepositoryFromLib
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.statusbar.CommandQueue
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.scan
-import kotlinx.coroutines.flow.stateIn
 
-/** Repository for providing access to display related information and events. */
-interface DisplayRepository : DisplayRepositoryFromLib {
-
-    /** A [StateFlow] that maintains a set of display IDs that should have system decorations. */
-    val displayIdsWithSystemDecorations: StateFlow<Set<Int>>
-}
+/**
+ * Repository for providing access to display related information and events.
+ *
+ * This is now just an interface that extends [DisplayRepositoryFromLib] to avoid changing all the
+ * imports in sysui using this interface.
+ */
+interface DisplayRepository : DisplayRepositoryFromLib, DisplaysWithDecorationsRepository
 
 @SysUISingleton
-@SuppressLint("SharedFlowCreation")
 class DisplayRepositoryImpl
 @Inject
 constructor(
-    private val commandQueue: CommandQueue,
-    private val windowManager: IWindowManager,
-    @Background bgApplicationScope: CoroutineScope,
     private val displayRepositoryFromLib: com.android.app.displaylib.DisplayRepository,
-) : DisplayRepositoryFromLib by displayRepositoryFromLib, DisplayRepository {
-
-    private val decorationEvents: Flow<Event> = callbackFlow {
-        val callback =
-            object : CommandQueue.Callbacks {
-                override fun onDisplayAddSystemDecorations(displayId: Int) {
-                    trySend(Event.Add(displayId))
-                }
-
-                override fun onDisplayRemoveSystemDecorations(displayId: Int) {
-                    trySend(Event.Remove(displayId))
-                }
-            }
-        commandQueue.addCallback(callback)
-        awaitClose { commandQueue.removeCallback(callback) }
-    }
-
-    private val initialDisplayIdsWithDecorations: Set<Int> =
-        displayIds.value.filter { windowManager.shouldShowSystemDecors(it) }.toSet()
-
-    /**
-     * A [StateFlow] that maintains a set of display IDs that should have system decorations.
-     *
-     * Updates to the set are triggered by:
-     * - Adding displays via [CommandQueue.Callbacks.onDisplayAddSystemDecorations].
-     * - Removing displays via [CommandQueue.Callbacks.onDisplayRemoveSystemDecorations].
-     * - Removing displays via [displayRemovalEvent] emissions.
-     *
-     * The set is initialized with displays that qualify for system decorations based on
-     * [WindowManager.shouldShowSystemDecors].
-     */
-    override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> =
-        merge(decorationEvents, displayRemovalEvent.map { Event.Remove(it) })
-            .scan(initialDisplayIdsWithDecorations) { displayIds: Set<Int>, event: Event ->
-                when (event) {
-                    is Event.Add -> displayIds + event.displayId
-                    is Event.Remove -> displayIds - event.displayId
-                }
-            }
-            .distinctUntilChanged()
-            .stateIn(
-                scope = bgApplicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = initialDisplayIdsWithDecorations,
-            )
-
-    private sealed class Event(val displayId: Int) {
-        class Add(displayId: Int) : Event(displayId)
-
-        class Remove(displayId: Int) : Event(displayId)
-    }
-
-    private companion object {
-        const val TAG = "DisplayRepository"
-    }
-}
+    private val displaysWithDecorationsRepositoryImpl: DisplaysWithDecorationsRepository,
+) :
+    DisplayRepositoryFromLib by displayRepositoryFromLib,
+    DisplaysWithDecorationsRepository by displaysWithDecorationsRepositoryImpl,
+    DisplayRepository
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplaysWithDecorationsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplaysWithDecorationsRepository.kt
new file mode 100644
index 0000000..f4a2ed4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplaysWithDecorationsRepository.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.display.data.repository
+
+import android.view.IWindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.CommandQueue
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.flow.stateIn
+
+/** Provides the displays with decorations. */
+interface DisplaysWithDecorationsRepository {
+    /** A [StateFlow] that maintains a set of display IDs that should have system decorations. */
+    val displayIdsWithSystemDecorations: StateFlow<Set<Int>>
+}
+
+@SysUISingleton
+class DisplaysWithDecorationsRepositoryImpl
+@Inject
+constructor(
+    private val commandQueue: CommandQueue,
+    private val windowManager: IWindowManager,
+    @Background bgApplicationScope: CoroutineScope,
+    displayRepository: com.android.app.displaylib.DisplayRepository,
+) : DisplaysWithDecorationsRepository {
+
+    private val decorationEvents: Flow<Event> = callbackFlow {
+        val callback =
+            object : CommandQueue.Callbacks {
+                override fun onDisplayAddSystemDecorations(displayId: Int) {
+                    trySend(Event.Add(displayId))
+                }
+
+                override fun onDisplayRemoveSystemDecorations(displayId: Int) {
+                    trySend(Event.Remove(displayId))
+                }
+            }
+        commandQueue.addCallback(callback)
+        awaitClose { commandQueue.removeCallback(callback) }
+    }
+
+    private val initialDisplayIdsWithDecorations: Set<Int> =
+        displayRepository.displayIds.value
+            .filter { windowManager.shouldShowSystemDecors(it) }
+            .toSet()
+
+    /**
+     * A [StateFlow] that maintains a set of display IDs that should have system decorations.
+     *
+     * Updates to the set are triggered by:
+     * - Adding displays via [CommandQueue.Callbacks.onDisplayAddSystemDecorations].
+     * - Removing displays via [CommandQueue.Callbacks.onDisplayRemoveSystemDecorations].
+     * - Removing displays via [displayRemovalEvent] emissions.
+     *
+     * The set is initialized with displays that qualify for system decorations based on
+     * [WindowManager.shouldShowSystemDecors].
+     */
+    override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> =
+        merge(decorationEvents, displayRepository.displayRemovalEvent.map { Event.Remove(it) })
+            .scan(initialDisplayIdsWithDecorations) { displayIds: Set<Int>, event: Event ->
+                when (event) {
+                    is Event.Add -> displayIds + event.displayId
+                    is Event.Remove -> displayIds - event.displayId
+                }
+            }
+            .distinctUntilChanged()
+            .stateIn(
+                scope = bgApplicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = initialDisplayIdsWithDecorations,
+            )
+
+    private sealed class Event(val displayId: Int) {
+        class Add(displayId: Int) : Event(displayId)
+
+        class Remove(displayId: Int) : Event(displayId)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
index a56710e..86c9d84c 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.display.data.repository
 
+import com.android.app.displaylib.PerDisplayRepository
+
+// TODO b/401305290 - move to displaylib
 class FakePerDisplayRepository<T> : PerDisplayRepository<T> {
 
     private val instances = mutableMapOf<Int, T>()
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt
index 212d556..efbae5d 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.display.data.repository
 
+import com.android.app.displaylib.PerDisplayRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.DumpableFromToString
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
deleted file mode 100644
index 7e00c60..0000000
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * 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.display.data.repository
-
-import android.util.Log
-import android.view.Display
-import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.app.tracing.traceSection
-import com.android.systemui.dagger.qualifiers.Background
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import java.util.concurrent.ConcurrentHashMap
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collectLatest
-
-/**
- * Used to create instances of type `T` for a specific display.
- *
- * This is useful for resources or objects that need to be managed independently for each connected
- * display (e.g., UI state, rendering contexts, or display-specific configurations).
- *
- * Note that in most cases this can be implemented by a simple `@AssistedFactory` with `displayId`
- * parameter
- *
- * ```kotlin
- * class SomeType @AssistedInject constructor(@Assisted displayId: Int,..)
- *      @AssistedFactory
- *      interface Factory {
- *         fun create(displayId: Int): SomeType
- *      }
- *  }
- * ```
- *
- * Then it can be used to create a [PerDisplayRepository] as follows:
- * ```kotlin
- * // Injected:
- * val repositoryFactory: PerDisplayRepositoryImpl.Factory
- * val instanceFactory: PerDisplayRepositoryImpl.Factory
- * // repository creation:
- * repositoryFactory.create(instanceFactory::create)
- * ```
- *
- * @see PerDisplayRepository For how to retrieve and manage instances created by this factory.
- */
-fun interface PerDisplayInstanceProvider<T> {
-    /** Creates an instance for a display. */
-    fun createInstance(displayId: Int): T?
-}
-
-/**
- * Extends [PerDisplayInstanceProvider], adding support for destroying the instance.
- *
- * This is useful for releasing resources associated with a display when it is disconnected or when
- * the per-display instance is no longer needed.
- */
-interface PerDisplayInstanceProviderWithTeardown<T> : PerDisplayInstanceProvider<T> {
-    /** Destroys a previously created instance of `T` forever. */
-    fun destroyInstance(instance: T)
-}
-
-/**
- * Provides access to per-display instances of type `T`.
- *
- * Acts as a repository, managing the caching and retrieval of instances created by a
- * [PerDisplayInstanceProvider]. It ensures that only one instance of `T` exists per display ID.
- */
-interface PerDisplayRepository<T> {
-    /** Gets the cached instance or create a new one for a given display. */
-    operator fun get(displayId: Int): T?
-
-    /** Debug name for this repository, mainly for tracing and logging. */
-    val debugName: String
-
-    /**
-     * Callback to run when a given repository is initialized.
-     *
-     * This allows the caller to perform custom logic when the repository is ready to be used, e.g.
-     * register to dumpManager.
-     *
-     * Note that the instance is *leaked* outside of this class, so it should only be done when
-     * repository is meant to live as long as the caller. In systemUI this is ok because the
-     * repository lives as long as the process itself.
-     */
-    interface InitCallback {
-        fun onInit(debugName: String, instance: Any)
-    }
-}
-
-/**
- * Default implementation of [PerDisplayRepository].
- *
- * This class manages a cache of per-display instances of type `T`, creating them using a provided
- * [PerDisplayInstanceProvider] and optionally tearing them down using a
- * [PerDisplayInstanceProviderWithTeardown] when displays are disconnected.
- *
- * It listens to the [DisplayRepository] to detect when displays are added or removed, and
- * automatically manages the lifecycle of the per-display instances.
- *
- * Note that this is a [PerDisplayStoreImpl] 2.0 that doesn't require [CoreStartable] bindings,
- * providing all args in the constructor.
- */
-class PerDisplayInstanceRepositoryImpl<T>
-@AssistedInject
-constructor(
-    @Assisted override val debugName: String,
-    @Assisted private val instanceProvider: PerDisplayInstanceProvider<T>,
-    @Background private val backgroundApplicationScope: CoroutineScope,
-    private val displayRepository: DisplayRepository,
-    private val initCallback: PerDisplayRepository.InitCallback,
-) : PerDisplayRepository<T> {
-
-    private val perDisplayInstances = ConcurrentHashMap<Int, T?>()
-
-    init {
-        backgroundApplicationScope.launch("$debugName#start") { start() }
-    }
-
-    private suspend fun start() {
-        initCallback.onInit(debugName, this)
-        displayRepository.displayIds.collectLatest { displayIds ->
-            val toRemove = perDisplayInstances.keys - displayIds
-            toRemove.forEach { displayId ->
-                Log.d(TAG, "<$debugName> destroying instance for displayId=$displayId.")
-                perDisplayInstances.remove(displayId)?.let { instance ->
-                    (instanceProvider as? PerDisplayInstanceProviderWithTeardown)?.destroyInstance(
-                        instance
-                    )
-                }
-            }
-        }
-    }
-
-    override fun get(displayId: Int): T? {
-        if (displayRepository.getDisplay(displayId) == null) {
-            Log.e(TAG, "<$debugName: Display with id $displayId doesn't exist.")
-            return null
-        }
-
-        // If it doesn't exist, create it and put it in the map.
-        return perDisplayInstances.computeIfAbsent(displayId) { key ->
-            Log.d(TAG, "<$debugName> creating instance for displayId=$key, as it wasn't available.")
-            val instance =
-                traceSection({ "creating instance of $debugName for displayId=$key" }) {
-                    instanceProvider.createInstance(key)
-                }
-            if (instance == null) {
-                Log.e(
-                    TAG,
-                    "<$debugName> returning null because createInstance($key) returned null.",
-                )
-            }
-            instance
-        }
-    }
-
-    @AssistedFactory
-    interface Factory<T> {
-        fun create(
-            debugName: String,
-            instanceProvider: PerDisplayInstanceProvider<T>,
-        ): PerDisplayInstanceRepositoryImpl<T>
-    }
-
-    companion object {
-        private const val TAG = "PerDisplayInstanceRepo"
-    }
-
-    override fun toString(): String {
-        return "PerDisplayInstanceRepositoryImpl(" +
-            "debugName='$debugName', instances=$perDisplayInstances)"
-    }
-}
-
-/**
- * Provides an instance of a given class **only** for the default display, even if asked for another
- * display.
- *
- * This is useful in case of **flag refactors**: it can be provided instead of an instance of
- * [PerDisplayInstanceRepositoryImpl] when a flag related to multi display refactoring is off.
- *
- * Note that this still requires all instances to be provided by a [PerDisplayInstanceProvider]. If
- * you want to provide an existing instance instead for the default display, either implement it in
- * a custom [PerDisplayInstanceProvider] (e.g. inject it in the constructor and return it if the
- * displayId is zero), or use [SingleInstanceRepositoryImpl].
- */
-class DefaultDisplayOnlyInstanceRepositoryImpl<T>(
-    override val debugName: String,
-    private val instanceProvider: PerDisplayInstanceProvider<T>,
-) : PerDisplayRepository<T> {
-    private val lazyDefaultDisplayInstance by lazy {
-        instanceProvider.createInstance(Display.DEFAULT_DISPLAY)
-    }
-
-    override fun get(displayId: Int): T? = lazyDefaultDisplayInstance
-}
-
-/**
- * Always returns [instance] for any display.
- *
- * This can be used to provide a single instance based on a flag value during a refactor. Similar to
- * [DefaultDisplayOnlyInstanceRepositoryImpl], but also avoids creating the
- * [PerDisplayInstanceProvider]. This is useful when you want to provide an existing instance only,
- * without even instantiating a [PerDisplayInstanceProvider].
- */
-class SingleInstanceRepositoryImpl<T>(override val debugName: String, private val instance: T) :
-    PerDisplayRepository<T> {
-    override fun get(displayId: Int): T? = instance
-}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 9def81a..2b16e19 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -18,7 +18,6 @@
 
 import static com.android.systemui.doze.DozeMachine.State.DOZE;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
-import static com.android.systemui.Flags.dozeuiSchedulingAlarmsBackgroundExecution;
 
 import android.app.AlarmManager;
 import android.content.Context;
@@ -84,13 +83,7 @@
         mBgExecutor = bgExecutor;
         mCanAnimateTransition = !params.getDisplayNeedsBlanking();
         mDozeParameters = params;
-        if (dozeuiSchedulingAlarmsBackgroundExecution()) {
-            mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick",
-                    bgHandler);
-        } else {
-            mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick",
-                    handler);
-        }
+        mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", bgHandler);
         mDozeLog = dozeLog;
     }
 
@@ -184,7 +177,7 @@
         mTimeTickScheduled = true;
 
         long time = System.currentTimeMillis();
-        long delta = roundToNextMinute(time) - System.currentTimeMillis();
+        long delta = roundToNextMinute(time) - time;
         boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED);
         if (scheduled) {
             mDozeLog.traceTimeTickScheduled(time, time + delta);
@@ -224,14 +217,8 @@
     private void onTimeTick() {
         verifyLastTimeTick();
 
-        if (dozeuiSchedulingAlarmsBackgroundExecution()) {
-            mHandler.post(mHost::dozeTimeTick);
-        } else {
-            mHost.dozeTimeTick();
-        }
-
         // Keep wakelock until a frame has been pushed.
-        mHandler.post(mWakeLock.wrap(() -> {}));
+        mHandler.post(mWakeLock.wrap(mHost::dozeTimeTick));
 
         mTimeTickScheduled = false;
         scheduleTimeTick();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index c61530c..74cf7e4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -20,7 +20,6 @@
 import android.content.Context
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.lightRevealMigration
 import com.android.systemui.biometrics.ui.binder.DeviceEntryUnlockTrackerViewBinder
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.dagger.SysUISingleton
@@ -46,6 +45,7 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shared.Flags.ambientAod
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.VibratorHelper
@@ -105,7 +105,7 @@
         bindJankViewModel()
         initializeViews()
 
-        if (lightRevealMigration()) {
+        if (ambientAod()) {
             LightRevealScrimViewBinder.bind(
                 lightRevealScrim,
                 lightRevealScrimViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4755e28..6db2ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1744,6 +1744,9 @@
         mJavaAdapter.alwaysCollectFlow(
                 mWallpaperRepository.getWallpaperSupportsAmbientMode(),
                 this::setWallpaperSupportsAmbientMode);
+        mJavaAdapter.alwaysCollectFlow(
+                mKeyguardInteractor.getDozeTimeTick(),
+                this::triggerTimeUpdate);
     }
 
     @Override
@@ -4056,6 +4059,10 @@
         mWallpaperSupportsAmbientMode = supportsAmbientMode;
     }
 
+    private void triggerTimeUpdate(long timeInMillis) {
+        mUpdateMonitor.triggerTimeUpdate();
+    }
+
     private static class StartKeyguardExitAnimParams {
 
         @WindowManager.TransitionOldType int mTransit;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 0b116de..438dff9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -139,7 +139,7 @@
     fun setWallpaperSupportsAmbientMode(supportsAmbientMode: Boolean) {
         repository.maxAlpha.value =
             if (supportsAmbientMode) {
-                0.7f
+                0.54f
             } else {
                 1f
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt
index 45f8f10..3cf0506 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGlanceableHubTransitionViewModel.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.util.MathUtils
-import com.android.systemui.Flags.lightRevealMigration
 import com.android.systemui.communal.ui.compose.TransitionDuration
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.dagger.GlanceableHubBlurComponent
@@ -28,6 +27,7 @@
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.keyguard.ui.transitions.GlanceableHubTransition
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shared.Flags.ambientAod
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
@@ -56,14 +56,14 @@
         return transitionAnimation.sharedFlow(
             duration = 250.milliseconds,
             startTime =
-                if (lightRevealMigration()) {
+                if (ambientAod()) {
                     100.milliseconds // Wait for the light reveal to "hit" the LS elements.
                 } else {
                     0.milliseconds
                 },
             onStart = {
                 currentAlpha =
-                    if (lightRevealMigration()) {
+                    if (ambientAod()) {
                         viewState.alpha()
                     } else {
                         0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
index d981eeb..ba6bda8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.util.MathUtils
-import com.android.systemui.Flags.lightRevealMigration
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
@@ -25,6 +24,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.shared.Flags.ambientAod
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
@@ -52,13 +52,13 @@
         return transitionAnimation.sharedFlow(
             duration = 250.milliseconds,
             startTime =
-                if (lightRevealMigration()) {
+                if (ambientAod()) {
                     100.milliseconds // Wait for the light reveal to "hit" the LS elements.
                 } else {
                     0.milliseconds
                 },
             onStart = {
-                if (lightRevealMigration()) {
+                if (ambientAod()) {
                     currentAlpha = viewState.alpha()
                 } else {
                     currentAlpha = 0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
index b15cacf..2eb5bf9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.util.MathUtils
-import com.android.systemui.Flags.lightRevealMigration
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
@@ -25,6 +24,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.shared.Flags.ambientAod
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
@@ -56,7 +56,7 @@
             duration = 250.milliseconds,
             startTime = 0.milliseconds,
             onStart = {
-                if (lightRevealMigration()) {
+                if (ambientAod()) {
                     currentAlpha = viewState.alpha()
                 } else {
                     currentAlpha = 0f
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
index c6e4db7..324a3ef 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
@@ -150,6 +150,13 @@
             titleTextView.isEnabled = true
         } else {
             errorTextView.visibility = View.VISIBLE
+            if (com.android.systemui.Flags.mediaProjectionGreyErrorText()) {
+                errorTextView.isEnabled = false
+                errorTextView.setTextColor(context.getColorStateList(R.color.menu_item_text))
+                errorTextView.setText(
+                    R.string.media_projection_entry_app_permission_dialog_single_app_not_supported
+                )
+            }
             titleTextView.isEnabled = false
         }
         return view
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
index 71cb745..68cd807 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
@@ -17,9 +17,9 @@
 
 import android.util.Log
 import android.view.Display
+import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.display.data.repository.PerDisplayInstanceProviderWithTeardown
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.model.SysUiState.SysUiStateCallback
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
index 58ddbf6..39482be 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
@@ -24,10 +24,10 @@
 import android.view.View;
 import android.view.WindowManager;
 
+import com.android.app.displaylib.PerDisplayRepository;
 import com.android.app.viewcapture.ViewCapture;
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.dagger.qualifiers.DisplayId;
-import com.android.systemui.display.data.repository.PerDisplayRepository;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
 import com.android.systemui.navigationbar.views.NavigationBarFrame;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 237ec7c..6cda192 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -22,6 +22,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 
 import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground;
+import static com.android.systemui.Flags.predictiveBackDelayWmTransition;
 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
 import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED;
@@ -1182,6 +1183,9 @@
                         return;
                     } else if (dx > dy && dx > mTouchSlop) {
                         if (mAllowGesture) {
+                            if (!predictiveBackDelayWmTransition() && mBackAnimation != null) {
+                                mBackAnimation.onThresholdCrossed();
+                            }
                             if (mBackAnimation == null) {
                                 pilferPointers();
                             }
@@ -1197,7 +1201,8 @@
                 // forward touch
                 mEdgeBackPlugin.onMotionEvent(ev);
                 dispatchToBackAnimation(ev);
-                if (mBackAnimation != null && mThresholdCrossed && !mLastFrameThresholdCrossed) {
+                if (predictiveBackDelayWmTransition() && mBackAnimation != null
+                        && mThresholdCrossed && !mLastFrameThresholdCrossed) {
                     mBackAnimation.onThresholdCrossed();
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index d20b360..728652e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -82,6 +82,7 @@
                         viewModel.tileHapticsViewModelFactoryProvider,
                     // There should be no QuickQuickSettings when the details view is enabled.
                     detailsViewModel = null,
+                    isVisible = listening,
                 )
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
index d40ecc9..1176095 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.panels.ui.compose
 
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
@@ -23,11 +24,13 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.ArrowBack
 import androidx.compose.material.icons.filled.Settings
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
@@ -63,6 +66,7 @@
 
     val title = tileDetailedViewModel.title
     val subTitle = tileDetailedViewModel.subTitle
+    val colors = MaterialTheme.colorScheme
 
     Column(
         modifier =
@@ -70,20 +74,33 @@
                 .fillMaxWidth()
                 // The height of the details view is TBD.
                 .fillMaxHeight()
+                .background(color = colors.onPrimary)
     ) {
         CompositionLocalProvider(
             value = LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant
         ) {
             Row(
-                modifier = Modifier.fillMaxWidth(),
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(
+                        start = TileDetailsDefaults.TitleRowStart,
+                        top = TileDetailsDefaults.TitleRowTop,
+                        end = TileDetailsDefaults.TitleRowEnd,
+                        bottom = TileDetailsDefaults.TitleRowBottom
+                    ),
                 horizontalArrangement = Arrangement.SpaceBetween,
                 verticalAlignment = Alignment.CenterVertically,
             ) {
                 IconButton(
                     onClick = { detailsViewModel.closeDetailedView() },
+                    colors = IconButtonDefaults.iconButtonColors(
+                        contentColor = colors.onSurface
+                    ),
                     modifier =
-                        Modifier.align(Alignment.CenterVertically)
+                        Modifier
+                            .align(Alignment.CenterVertically)
                             .height(TileDetailsDefaults.IconHeight)
+                            .width(TileDetailsDefaults.IconWidth)
                             .padding(start = TileDetailsDefaults.IconPadding),
                 ) {
                     Icon(
@@ -96,13 +113,19 @@
                     text = title,
                     modifier = Modifier.align(Alignment.CenterVertically),
                     textAlign = TextAlign.Center,
-                    style = MaterialTheme.typography.titleLarge,
+                    style = MaterialTheme.typography.titleMedium,
+                    color = colors.onSurface,
                 )
                 IconButton(
                     onClick = { tileDetailedViewModel.clickOnSettingsButton() },
+                    colors = IconButtonDefaults.iconButtonColors(
+                        contentColor = colors.onSurface
+                    ),
                     modifier =
-                        Modifier.align(Alignment.CenterVertically)
+                        Modifier
+                            .align(Alignment.CenterVertically)
                             .height(TileDetailsDefaults.IconHeight)
+                            .width(TileDetailsDefaults.IconWidth)
                             .padding(end = TileDetailsDefaults.IconPadding),
                 ) {
                     Icon(
@@ -116,7 +139,8 @@
                 text = subTitle,
                 modifier = Modifier.fillMaxWidth(),
                 textAlign = TextAlign.Center,
-                style = MaterialTheme.typography.titleSmall,
+                style = MaterialTheme.typography.bodySmall,
+                color = colors.onSurfaceVariant,
             )
         }
         MapTileDetailsContent(tileDetailedViewModel)
@@ -135,6 +159,11 @@
 }
 
 private object TileDetailsDefaults {
-    val IconHeight = 48.dp
+    val IconHeight = 24.dp
+    val IconWidth = 24.dp
     val IconPadding = 4.dp
+    val TitleRowStart = 14.dp
+    val TitleRowTop = 22.dp
+    val TitleRowEnd = 20.dp
+    val TitleRowBottom = 8.dp
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 50012ab..699778f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -39,12 +39,14 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.text.BasicText
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
@@ -102,6 +104,7 @@
     sideDrawable: Drawable?,
     colors: TileColors,
     squishiness: () -> Float,
+    isVisible: () -> Boolean = { true },
     accessibilityUiState: AccessibilityUiState? = null,
     iconShape: RoundedCornerShape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
     toggleClick: (() -> Unit)? = null,
@@ -158,6 +161,7 @@
             secondaryLabel = secondaryLabel,
             colors = colors,
             accessibilityUiState = accessibilityUiState,
+            isVisible = isVisible,
         )
 
         if (sideDrawable != null) {
@@ -170,12 +174,14 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun LargeTileLabels(
     label: String,
     secondaryLabel: String?,
     colors: TileColors,
     modifier: Modifier = Modifier,
+    isVisible: () -> Boolean = { true },
     accessibilityUiState: AccessibilityUiState? = null,
 ) {
     val animatedLabelColor by animateColorAsState(colors.label, label = "QSTileLabelColor")
@@ -184,14 +190,16 @@
     Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) {
         TileLabel(
             text = label,
-            style = MaterialTheme.typography.labelLarge,
+            style = MaterialTheme.typography.titleSmallEmphasized,
             color = { animatedLabelColor },
+            isVisible = isVisible,
         )
         if (!TextUtils.isEmpty(secondaryLabel)) {
             TileLabel(
                 secondaryLabel ?: "",
                 color = { animatedSecondaryLabelColor },
-                style = MaterialTheme.typography.bodyMedium,
+                style = MaterialTheme.typography.labelMedium,
+                isVisible = isVisible,
                 modifier =
                     Modifier.thenIf(
                         accessibilityUiState?.stateDescription?.contains(secondaryLabel ?: "") ==
@@ -277,36 +285,50 @@
     color: ColorProducer,
     style: TextStyle,
     modifier: Modifier = Modifier,
+    isVisible: () -> Boolean = { true },
 ) {
+    var textSize by remember { mutableIntStateOf(0) }
+
     BasicText(
         text = text,
         color = color,
         style = style,
         maxLines = 1,
+        onTextLayout = { textSize = it.size.width },
         modifier =
             modifier
                 .fillMaxWidth()
-                .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
+                .graphicsLayer {
+                    if (textSize > size.width) {
+                        compositingStrategy = CompositingStrategy.Offscreen
+                    }
+                }
                 .drawWithContent {
                     drawContent()
-                    // Draw a blur over the end of the text
-                    val edgeWidthPx = TileLabelBlurWidth.toPx()
-                    drawRect(
-                        topLeft = Offset(size.width - edgeWidthPx, 0f),
-                        size = Size(edgeWidthPx, size.height),
-                        brush =
-                            Brush.horizontalGradient(
-                                colors = listOf(Color.Transparent, Color.Black),
-                                startX = size.width,
-                                endX = size.width - edgeWidthPx,
-                            ),
-                        blendMode = BlendMode.DstIn,
-                    )
+                    if (textSize > size.width) {
+                        // Draw a blur over the end of the text
+                        val edgeWidthPx = TileLabelBlurWidth.toPx()
+                        drawRect(
+                            topLeft = Offset(size.width - edgeWidthPx, 0f),
+                            size = Size(edgeWidthPx, size.height),
+                            brush =
+                                Brush.horizontalGradient(
+                                    colors = listOf(Color.Transparent, Color.Black),
+                                    startX = size.width,
+                                    endX = size.width - edgeWidthPx,
+                                ),
+                            blendMode = BlendMode.DstIn,
+                        )
+                    }
                 }
-                .basicMarquee(
-                    iterations = TILE_MARQUEE_ITERATIONS,
-                    initialDelayMillis = TILE_INITIAL_DELAY_MILLIS,
-                ),
+                .thenIf(isVisible()) {
+                    // Only apply the marquee when the label is visible, which is needed for the
+                    // always composed QS
+                    Modifier.basicMarquee(
+                        iterations = TILE_MARQUEE_ITERATIONS,
+                        initialDelayMillis = TILE_INITIAL_DELAY_MILLIS,
+                    )
+                },
     )
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index ccbd8fd..46f05d0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -65,6 +65,7 @@
 import androidx.compose.material.icons.filled.Clear
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.LocalContentColor
@@ -109,11 +110,11 @@
 import androidx.compose.ui.semantics.customActions
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.text.style.Hyphens
 import androidx.compose.ui.text.style.TextAlign
 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.util.fastMap
 import com.android.compose.gesture.effect.rememberOffsetOverscrollEffectFactory
 import com.android.compose.modifiers.height
@@ -165,7 +166,7 @@
 
 object TileType
 
-@OptIn(ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) {
     val primaryContainerColor = MaterialTheme.colorScheme.primaryContainer
@@ -177,7 +178,8 @@
             ),
         title = {
             Text(
-                text = stringResource(id = R.string.qs_edit),
+                text = stringResource(id = R.string.qs_edit_tiles),
+                style = MaterialTheme.typography.titleLargeEmphasized,
                 modifier = Modifier.padding(start = 24.dp),
             )
         },
@@ -204,7 +206,10 @@
                             contentColor = MaterialTheme.colorScheme.onPrimary,
                         ),
                 ) {
-                    Text(stringResource(id = com.android.internal.R.string.reset))
+                    Text(
+                        text = stringResource(id = com.android.internal.R.string.reset),
+                        style = MaterialTheme.typography.labelLarge,
+                    )
                 }
             }
         },
@@ -212,6 +217,7 @@
     )
 }
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun DefaultEditTileGrid(
     listState: EditTileListState,
@@ -283,7 +289,9 @@
                                 }
                             }
                         } else {
-                            Text(text = stringResource(id = R.string.drag_to_rearrange_tiles))
+                            EditGridCenteredText(
+                                text = stringResource(id = R.string.drag_to_rearrange_tiles)
+                            )
                         }
                     }
                 }
@@ -401,6 +409,11 @@
 }
 
 @Composable
+private fun EditGridCenteredText(text: String, modifier: Modifier = Modifier) {
+    Text(text = text, style = MaterialTheme.typography.titleSmall, modifier = modifier)
+}
+
+@Composable
 private fun RemoveTileTarget(onClick: () -> Unit) {
     Row(
         verticalAlignment = Alignment.CenterVertically,
@@ -486,6 +499,7 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun AvailableTileGrid(
     tiles: List<AvailableTileGridCell>,
@@ -524,7 +538,7 @@
                 ) {
                     Text(
                         text = category.label.load() ?: "",
-                        fontSize = 20.sp,
+                        style = MaterialTheme.typography.titleMediumEmphasized,
                         color = MaterialTheme.colorScheme.onSurface,
                         modifier = Modifier.fillMaxWidth().padding(start = 8.dp, bottom = 16.dp),
                     )
@@ -737,6 +751,7 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 private fun AvailableTileGridCell(
     cell: AvailableTileGridCell,
@@ -803,6 +818,7 @@
                 color = colors.label,
                 overflow = TextOverflow.Ellipsis,
                 textAlign = TextAlign.Center,
+                style = MaterialTheme.typography.labelMedium.copy(hyphens = Hyphens.Auto),
                 modifier = Modifier.align(Alignment.TopCenter),
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 27e6092..984343a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -119,6 +119,7 @@
                             isLastInRow = isLastInColumn,
                         ),
                     detailsViewModel = detailsViewModel,
+                    isVisible = listening,
                 )
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index 6bafd43..e247182 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -139,6 +139,7 @@
     bounceableInfo: BounceableInfo,
     tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
     modifier: Modifier = Modifier,
+    isVisible: () -> Boolean = { true },
     detailsViewModel: DetailsViewModel?,
 ) {
     trace(tile.traceName) {
@@ -249,6 +250,7 @@
                         onLongClick = longClick,
                         accessibilityUiState = uiState.accessibilityUiState,
                         squishiness = squishiness,
+                        isVisible = isVisible,
                     )
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt
index 3287443..60ca6e6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt
@@ -23,11 +23,15 @@
 import com.android.systemui.plugins.qs.TileDetailsViewModel
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
 import javax.inject.Inject
 
 @SysUISingleton
 @Stable
-class DetailsViewModel @Inject constructor(val currentTilesInteractor: CurrentTilesInteractor) {
+class DetailsViewModel @Inject constructor(
+    val currentTilesInteractor: CurrentTilesInteractor,
+    val shadeModeInteractor: ShadeModeInteractor
+) {
 
     /**
      * The current active [TileDetailsViewModel]. If it's `null`, it means the qs overlay is not
@@ -52,6 +56,10 @@
      * @see activeTileDetails
      */
     fun onTileClicked(spec: TileSpec?): Boolean {
+        if (!shadeModeInteractor.isDualShade){
+            return false
+        }
+
         if (spec == null) {
             _activeTileDetails.value = null
             return false
diff --git a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
index d263965..5b97175 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
@@ -78,6 +78,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.app.displaylib.PerDisplayRepository;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -90,7 +91,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.display.data.repository.DisplayRepository;
-import com.android.systemui.display.data.repository.PerDisplayRepository;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardWmStateRefactor;
@@ -126,8 +126,6 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInterface;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -139,6 +137,8 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import dagger.Lazy;
+
 /**
  * Class to send information from SysUI to Launcher with a binder.
  */
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 922afc7..9cae6c1 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
@@ -329,6 +329,7 @@
                     cookie,
                     component,
                     launchCujType = Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+                    returnCujType = Cuj.CUJ_STATUS_BAR_APP_RETURN_TO_CALL_CHIP,
                 ) {
                 override suspend fun createController(
                     forLaunch: Boolean
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 74b3f30..b94e7b2 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
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
@@ -36,8 +37,10 @@
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.Expandable
+import com.android.compose.modifiers.thenIf
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.common.ui.compose.load
@@ -79,6 +82,17 @@
                 }
             is OngoingActivityChipModel.ClickBehavior.None -> null
         }
+    val isClickable = onClick != null
+
+    val chipSidePadding = dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
+    val minWidth =
+        if (isClickable) {
+            dimensionResource(id = R.dimen.min_clickable_item_size)
+        } else if (model.icon != null) {
+            dimensionResource(id = R.dimen.ongoing_activity_chip_icon_size) + chipSidePadding
+        } else {
+            dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding
+        }
 
     Expandable(
         color = Color(model.colors.background(LocalContext.current).defaultColor),
@@ -92,6 +106,15 @@
                         this.contentDescription = contentDescription
                     }
                 }
+                .thenIf(isClickable) { Modifier.widthIn(min = minWidth) }
+                .layout { measurable, constraints ->
+                    val placeable = measurable.measure(constraints)
+                    layout(placeable.width, placeable.height) {
+                        if (constraints.maxWidth >= minWidth.roundToPx()) {
+                            placeable.place(0, 0)
+                        }
+                    }
+                }
                 .graphicsLayer(
                     alpha =
                         if (model.transitionManager?.hideChipForTransition == true) {
@@ -103,9 +126,12 @@
         borderStroke = borderStroke,
         onClick = onClick,
         useModifierBasedImplementation = StatusBarChipsReturnAnimations.isEnabled,
+        // Some chips like the 3-2-1 countdown chip should be very small, smaller than a
+        // reasonable minimum size.
+        defaultMinSize = false,
         transitionControllerFactory = model.transitionManager?.controllerFactory,
     ) {
-        ChipBody(model, iconViewStore, isClickable = onClick != null)
+        ChipBody(model, iconViewStore, isClickable = isClickable, minWidth = minWidth)
     }
 }
 
@@ -114,36 +140,22 @@
     model: OngoingActivityChipModel.Active,
     iconViewStore: NotificationIconContainerViewBinder.IconViewStore?,
     isClickable: Boolean,
+    minWidth: Dp,
     modifier: Modifier = Modifier,
 ) {
     val hasEmbeddedIcon =
         model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView ||
             model.icon is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
 
-    val chipSidePadding = dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
-    val minWidth =
-        if (isClickable) {
-            dimensionResource(id = R.dimen.min_clickable_item_size)
-        } else if (model.icon != null) {
-            dimensionResource(id = R.dimen.ongoing_activity_chip_icon_size) + chipSidePadding
-        } else {
-            dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding
-        }
-
     Row(
         horizontalArrangement = Arrangement.Center,
         verticalAlignment = Alignment.CenterVertically,
         modifier =
             modifier
                 .fillMaxHeight()
-                .layout { measurable, constraints ->
-                    val placeable = measurable.measure(constraints)
-                    layout(placeable.width, placeable.height) {
-                        if (constraints.maxWidth >= minWidth.roundToPx()) {
-                            placeable.place(0, 0)
-                        }
-                    }
-                }
+                // Set the minWidth here as well as on the Expandable so that the content within
+                // this row is still centered correctly horizontally
+                .thenIf(isClickable) { Modifier.widthIn(min = minWidth) }
                 .padding(
                     horizontal =
                         if (hasEmbeddedIcon) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt
index 3168a22..cb1002a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayConfigurationStateRepository.kt
@@ -17,14 +17,14 @@
 package com.android.systemui.statusbar.data.repository
 
 import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
+import com.android.app.displaylib.PerDisplayInstanceProvider
+import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl
+import com.android.app.displaylib.PerDisplayRepository
+import com.android.app.displaylib.SingleInstanceRepositoryImpl
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.common.ui.ConfigurationStateImpl
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
-import com.android.systemui.display.data.repository.PerDisplayInstanceProvider
-import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl
-import com.android.systemui.display.data.repository.PerDisplayRepository
-import com.android.systemui.display.data.repository.SingleInstanceRepositoryImpl
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import dagger.Lazy
 import dagger.Module
@@ -39,7 +39,6 @@
     private val statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore,
     private val factory: ConfigurationStateImpl.Factory,
 ) : PerDisplayInstanceProvider<ConfigurationState> {
-
     override fun createInstance(displayId: Int): ConfigurationState? {
         val displayWindowProperties =
             displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index fcdcc3f..fed9417 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -23,6 +23,7 @@
 
 import com.android.systemui.DejankUtils;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
@@ -44,13 +45,20 @@
     private final Optional<Bubbles> mBubblesOptional;
     private final NotificationActivityStarter mNotificationActivityStarter;
 
-    private ExpandableNotificationRow.OnDragSuccessListener mOnDragSuccessListener =
-            new ExpandableNotificationRow.OnDragSuccessListener() {
-                @Override
-                public void onDragSuccess(NotificationEntry entry) {
-                    mNotificationActivityStarter.onDragSuccess(entry);
-                }
-            };
+    private ExpandableNotificationRow.OnDragSuccessListener mOnDragSuccessListener
+            = new ExpandableNotificationRow.OnDragSuccessListener() {
+        @Override
+        public void onDragSuccess(NotificationEntry entry) {
+            NotificationBundleUi.assertInLegacyMode();
+            mNotificationActivityStarter.onDragSuccess(entry);
+        }
+
+        @Override
+        public void onDragSuccess(EntryAdapter entryAdapter) {
+            NotificationBundleUi.isUnexpectedlyInLegacyMode();
+            entryAdapter.onDragSuccess();
+        }
+    };
 
     private NotificationClicker(
             NotificationClickerLogger logger,
@@ -73,7 +81,6 @@
         mPowerInteractor.wakeUpIfDozing("NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
 
         final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-        final NotificationEntry entry = row.getEntry();
         mLogger.logOnClick(row.getLoggingKey());
 
         // Check if the notification is displaying the menu, if so slide notification back
@@ -101,16 +108,16 @@
         DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
 
         if (NotificationBundleUi.isEnabled()) {
-            if (!row.getEntryAdapter().isBubbleCapable() && mBubblesOptional.isPresent()) {
+            if (!row.getEntryAdapter().isBubble() && mBubblesOptional.isPresent()) {
                 mBubblesOptional.get().collapseStack();
             }
+            row.getEntryAdapter().onEntryClicked(row);
         } else {
             if (!row.getEntryLegacy().isBubble() && mBubblesOptional.isPresent()) {
                 mBubblesOptional.get().collapseStack();
             }
+            mNotificationActivityStarter.onNotificationClicked(row.getEntryLegacy(), row);
         }
-
-        mNotificationActivityStarter.onNotificationClicked(entry, row);
     }
 
     private boolean isMenuVisible(ExpandableNotificationRow row) {
@@ -121,9 +128,12 @@
      * Attaches the click listener to the row if appropriate.
      */
     public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
+        boolean isBubble = NotificationBundleUi.isEnabled()
+                ? row.getEntryAdapter().isBubble()
+                : row.getEntryLegacy().isBubble();
         Notification notification = sbn.getNotification();
         if (notification.contentIntent != null || notification.fullScreenIntent != null
-                || row.getEntry().isBubble()) {
+                || isBubble) {
             if (NotificationBundleUi.isEnabled()) {
                 row.setBubbleClickListener(
                         v -> row.getEntryAdapter().onNotificationBubbleIconClicked());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
index d06f24f..ba40010 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
@@ -17,3 +17,4 @@
 yurilin@google.com
 
 per-file MediaNotificationProcessor.java = ethibodeau@google.com
+per-file MagicActionBackgroundDrawable.kt = dupin@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
index f653573..8fc6cbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
@@ -69,10 +69,13 @@
         return mUnmodifiableChildren;
     }
 
+    void clearChildren() {
+        mChildren.clear();
+    }
+
     /**
      * @return Null because bundles do not have an associated NotificationEntry.
      */
-
     @Nullable
     @Override
     public NotificationEntry getRepresentativeEntry() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
index 1fc8efd..be17ae56c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
@@ -22,6 +22,7 @@
 import android.service.notification.StatusBarNotification
 import android.util.Log
 import com.android.systemui.statusbar.notification.icon.IconPack
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import kotlinx.coroutines.flow.StateFlow
 
@@ -97,7 +98,7 @@
         return false
     }
 
-    override fun isBubbleCapable(): Boolean {
+    override fun isBubble(): Boolean {
         return false
     }
 
@@ -113,6 +114,10 @@
         return false
     }
 
+    override fun getPeopleNotificationType(): Int {
+        return TYPE_NON_PERSON
+    }
+
     override fun isPromotedOngoing(): Boolean {
         return false
     }
@@ -121,6 +126,11 @@
         return false
     }
 
+    override fun onDragSuccess() {
+        // do nothing. these should not be draggable
+        Log.wtf(TAG, "onDragSuccess() called")
+    }
+
     override fun onNotificationBubbleIconClicked() {
         // do nothing. these cannot be a bubble
         Log.wtf(TAG, "onNotificationBubbleIconClicked() called")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
index 47ffe10..3757ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
@@ -23,6 +23,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.statusbar.notification.icon.IconPack;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 import kotlinx.coroutines.flow.StateFlow;
@@ -124,7 +125,7 @@
 
     boolean canDragAndDrop();
 
-    boolean isBubbleCapable();
+    boolean isBubble();
 
     @Nullable String getStyle();
 
@@ -132,6 +133,8 @@
 
     boolean isAmbient();
 
+    @PeopleNotificationIdentifier.Companion.PeopleNotificationType int getPeopleNotificationType();
+
     /**
      * Returns whether this row represents promoted ongoing notification.
      */
@@ -141,6 +144,8 @@
         return false;
     }
 
+    void onDragSuccess();
+
     /**
      * Process a click on a notification bubble icon
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
index f78a5dd..a23c5a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
@@ -121,7 +121,7 @@
         return false
     }
 
-    override fun isBubbleCapable(): Boolean {
+    override fun isBubble(): Boolean {
         return entry.isBubble
     }
 
@@ -137,6 +137,10 @@
         return entry.ranking.isAmbient
     }
 
+    override fun getPeopleNotificationType(): Int {
+        return peopleNotificationIdentifier.getPeopleNotificationType(entry)
+    }
+
     override fun isPromotedOngoing(): Boolean {
         return entry.isPromotedOngoing
     }
@@ -145,6 +149,10 @@
         return entry.sbn.notification.fullScreenIntent != null
     }
 
+    override fun onDragSuccess() {
+        notificationActivityStarter.onDragSuccess(entry)
+    }
+
     override fun onNotificationBubbleIconClicked() {
         notificationActivityStarter.onNotificationBubbleIconClicked(entry)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 5cea821..fe2bd34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -567,7 +567,7 @@
 
         for (BundleEntry be : mIdToBundleEntry.values()) {
             be.beginNewAttachState();
-            // TODO(b/399736937) Clear bundle children
+            be.clearChildren();
             // BundleEntry has not representative summary so we do not need to clear it here.
         }
         mNotifList.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
index 2f0701f..3747aba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.util.ArrayMap
+import com.android.systemui.statusbar.notification.collection.BundleEntry
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.PipelineEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -37,9 +38,17 @@
     private fun onBeforeFinalizeFilter(entries: List<PipelineEntry>) {
         // save untruncated child counts to our internal map
         untruncatedChildCounts.clear()
-        entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
-            untruncatedChildCounts[groupEntry] = groupEntry.children.size
-        }
+        entries.asSequence()
+            .flatMap { entry ->
+                when (entry) {
+                    is GroupEntry -> listOf(entry)
+                    is BundleEntry -> entry.children.filterIsInstance<GroupEntry>()
+                    else -> emptyList()
+                }
+            }
+            .forEach { groupEntry ->
+                untruncatedChildCounts[groupEntry] = groupEntry.children.size
+            }
     }
 
     private fun onAfterRenderGroup(group: GroupEntry, controller: NotifGroupController) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 1be415d..20169ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -36,6 +36,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.statusbar.notification.collection.BundleEntry;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.PipelineEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -307,8 +308,15 @@
     private void inflateAllRequiredViews(List<PipelineEntry> entries) {
         for (int i = 0, size = entries.size(); i < size; i++) {
             PipelineEntry entry = entries.get(i);
-            if (NotificationBundleUi.isEnabled() && entry instanceof BundleEntry) {
-                // TODO(b/399738511) Inflate bundle views.
+            if (NotificationBundleUi.isEnabled() && entry instanceof BundleEntry bundleEntry) {
+                for (ListEntry listEntry : bundleEntry.getChildren()) {
+                    if (listEntry instanceof GroupEntry groupEntry) {
+                        inflateRequiredGroupViews(groupEntry);
+                    } else {
+                        NotificationEntry notifEntry = (NotificationEntry) listEntry;
+                        inflateRequiredNotifViews(notifEntry);
+                    }
+                }
             } else if (entry instanceof GroupEntry) {
                 GroupEntry groupEntry = (GroupEntry) entry;
                 inflateRequiredGroupViews(groupEntry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
index 147a5af..619d48f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
@@ -18,9 +18,9 @@
 
 import android.view.Display
 import androidx.lifecycle.lifecycleScope
+import com.android.app.displaylib.PerDisplayRepository
 import com.android.app.tracing.traceSection
 import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.display.data.repository.PerDisplayRepository
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index d35c3b61..7e19ff1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -21,6 +21,7 @@
 import android.app.Notification.BigTextStyle
 import android.app.Notification.CallStyle
 import android.app.Notification.EXTRA_BIG_TEXT
+import android.app.Notification.EXTRA_CALL_PERSON
 import android.app.Notification.EXTRA_CHRONOMETER_COUNT_DOWN
 import android.app.Notification.EXTRA_PROGRESS
 import android.app.Notification.EXTRA_PROGRESS_INDETERMINATE
@@ -33,6 +34,7 @@
 import android.app.Notification.EXTRA_VERIFICATION_TEXT
 import android.app.Notification.InboxStyle
 import android.app.Notification.ProgressStyle
+import android.app.Person
 import android.content.Context
 import android.graphics.drawable.Icon
 import com.android.systemui.Flags
@@ -108,12 +110,12 @@
         contentBuilder.shortCriticalText = notification.shortCriticalText()
         contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs
         contentBuilder.profileBadgeResId = null // TODO
-        contentBuilder.title = notification.resolveTitle(recoveredBuilder.style)
-        contentBuilder.text = notification.resolveText(recoveredBuilder.style)
+        contentBuilder.title = notification.title(recoveredBuilder.style)
+        contentBuilder.text = notification.text(recoveredBuilder.style)
         contentBuilder.skeletonLargeIcon = notification.skeletonLargeIcon(imageModelProvider)
         contentBuilder.oldProgress = notification.oldProgress()
 
-        val colorsFromNotif = recoveredBuilder.getColors(/* header= */ false)
+        val colorsFromNotif = recoveredBuilder.getColors(/* isHeader= */ false)
         contentBuilder.colors =
             PromotedNotificationContentModel.Colors(
                 backgroundColor = colorsFromNotif.backgroundColor,
@@ -132,20 +134,16 @@
 
     private fun Notification.bigTitle(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE_BIG)
 
-    private fun Notification.Style.bigTitleOverridesTitle(): Boolean {
-        return when (this) {
+    private fun Notification.callPerson(): Person? =
+        extras?.getParcelable(EXTRA_CALL_PERSON, Person::class.java)
+
+    private fun Notification.title(style: Notification.Style?): CharSequence? {
+        return when (style) {
             is BigTextStyle,
             is BigPictureStyle,
-            is InboxStyle -> true
-            else -> false
-        }
-    }
-
-    private fun Notification.resolveTitle(style: Notification.Style?): CharSequence? {
-        return if (style?.bigTitleOverridesTitle() == true) {
-            bigTitle()
-        } else {
-            null
+            is InboxStyle -> bigTitle()
+            is CallStyle -> callPerson()?.name
+            else -> null
         } ?: title()
     }
 
@@ -153,13 +151,10 @@
 
     private fun Notification.bigText(): CharSequence? = extras?.getCharSequence(EXTRA_BIG_TEXT)
 
-    private fun Notification.Style.bigTextOverridesText(): Boolean = this is BigTextStyle
-
-    private fun Notification.resolveText(style: Notification.Style?): CharSequence? {
-        return if (style?.bigTextOverridesText() == true) {
-            bigText()
-        } else {
-            null
+    private fun Notification.text(style: Notification.Style?): CharSequence? {
+        return when (style) {
+            is BigTextStyle -> bigText()
+            else -> null
         } ?: text()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
index 4689347..5f9678a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
@@ -63,7 +63,7 @@
             INFO,
             {
                 str1 = entry.logKey
-                str2 = content.toString()
+                str2 = content.toRedactedString()
             },
             { "extraction succeeded: $str2 for $str1" },
         )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
index 0c2859f..57b0720 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
@@ -25,6 +25,8 @@
 import com.android.internal.widget.NotificationProgressModel
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.row.ImageResult
+import com.android.systemui.statusbar.notification.row.LazyImage
 import com.android.systemui.statusbar.notification.row.shared.ImageModel
 
 /**
@@ -152,6 +154,54 @@
         Ineligible,
     }
 
+    fun toRedactedString(): String {
+        return ("PromotedNotificationContentModel(" +
+            "identity=$identity, " +
+            "wasPromotedAutomatically=$wasPromotedAutomatically, " +
+            "smallIcon=${smallIcon?.toRedactedString()}, " +
+            "appName=$appName, " +
+            "subText=${subText?.toRedactedString()}, " +
+            "shortCriticalText=$shortCriticalText, " +
+            "time=$time, " +
+            "lastAudiblyAlertedMs=$lastAudiblyAlertedMs, " +
+            "profileBadgeResId=$profileBadgeResId, " +
+            "title=${title?.toRedactedString()}, " +
+            "text=${text?.toRedactedString()}, " +
+            "skeletonLargeIcon=${skeletonLargeIcon?.toRedactedString()}, " +
+            "oldProgress=$oldProgress, " +
+            "colors=$colors, " +
+            "style=$style, " +
+            "personIcon=${personIcon?.toRedactedString()}, " +
+            "personName=${personName?.toRedactedString()}, " +
+            "verificationIcon=$verificationIcon, " +
+            "verificationText=$verificationText, " +
+            "newProgress=$newProgress)")
+    }
+
+    private fun CharSequence.toRedactedString(): String = "[$length]"
+
+    private fun ImageModel.toRedactedString(): String {
+        return when (this) {
+            is LazyImage -> this.toRedactedString()
+            else -> this.toString()
+        }
+    }
+
+    private fun LazyImage.toRedactedString(): String {
+        return ("LazyImage(" +
+            "icon=[${icon.javaClass.simpleName}], " +
+            "sizeClass=$sizeClass, " +
+            "transform=$transform, " +
+            "result=${result?.toRedactedString()})")
+    }
+
+    private fun ImageResult.toRedactedString(): String {
+        return when (this) {
+            is ImageResult.Empty -> this.toString()
+            is ImageResult.Image -> "Image(drawable=[${drawable.javaClass.simpleName}])"
+        }
+    }
+
     companion object {
         @JvmStatic
         fun featureFlagEnabled(): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 1a76d5d..2a3b266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -22,9 +22,9 @@
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_EXPANDED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
 
+import static com.android.systemui.Flags.notificationRowAccessibilityExpanded;
 import static com.android.systemui.Flags.notificationRowTransparency;
 import static com.android.systemui.Flags.notificationsPinnedHunInShade;
-import static com.android.systemui.Flags.notificationRowAccessibilityExpanded;
 import static com.android.systemui.flags.Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE;
 import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
@@ -671,8 +671,13 @@
     }
 
     private boolean isConversation() {
-        return mPeopleNotificationIdentifier.getPeopleNotificationType(getEntry())
-                != PeopleNotificationIdentifier.TYPE_NON_PERSON;
+        if (NotificationBundleUi.isEnabled()) {
+            return getEntryAdapter().getPeopleNotificationType()
+                    != PeopleNotificationIdentifier.TYPE_NON_PERSON;
+        } else {
+            return mPeopleNotificationIdentifier.getPeopleNotificationType(getEntryLegacy())
+                    != PeopleNotificationIdentifier.TYPE_NON_PERSON;
+        }
     }
 
     public void onNotificationUpdated() {
@@ -2515,7 +2520,11 @@
      */
     public void dragAndDropSuccess() {
         if (mOnDragSuccessListener != null) {
-            mOnDragSuccessListener.onDragSuccess(getEntry());
+            if (NotificationBundleUi.isEnabled()) {
+                mOnDragSuccessListener.onDragSuccess(getEntryAdapter());
+            } else {
+                mOnDragSuccessListener.onDragSuccess(getEntryLegacy());
+            }
         }
     }
 
@@ -4002,7 +4011,8 @@
             }
         } else if (isChildInGroup()) {
             final int childColor = getShowingLayout().getBackgroundColorForExpansionState();
-            if (Flags.notificationRowTransparency() && childColor == Color.TRANSPARENT) {
+            if ((Flags.notificationRowTransparency() || notificationsRedesignTemplates())
+                    && childColor == Color.TRANSPARENT) {
                 // If child is not customizing its background color, switch from the parent to
                 // the child background when the expansion finishes.
                 mShowNoBackground = !mNotificationParent.mShowNoBackground;
@@ -4416,6 +4426,12 @@
          * @param entry NotificationEntry that succeed to drop on proper target window.
          */
         void onDragSuccess(NotificationEntry entry);
+
+        /**
+         * @param entryAdapter The EntryAdapter that successfully dropped on the proper
+         *            target window
+         */
+        void onDragSuccess(EntryAdapter entryAdapter);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 292f74a..f36a0cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -19,6 +19,8 @@
 import static com.android.systemui.Flags.notificationColorUpdateLogger;
 import static com.android.systemui.Flags.physicalNotificationMovement;
 
+import static java.lang.Math.abs;
+
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -29,6 +31,7 @@
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.widget.FrameLayout;
@@ -110,14 +113,27 @@
     protected SpringAnimation mMagneticAnimator = new SpringAnimation(
             this /* object */, DynamicAnimation.TRANSLATION_X);
 
+    private int mTouchSlop;
+
     protected MagneticRowListener mMagneticRowListener = new MagneticRowListener() {
 
         @Override
-        public void setMagneticTranslation(float translation) {
-            if (mMagneticAnimator.isRunning()) {
-                mMagneticAnimator.animateToFinalPosition(translation);
-            } else {
+        public void setMagneticTranslation(float translation, boolean trackEagerly) {
+            if (!mMagneticAnimator.isRunning()) {
                 setTranslation(translation);
+                return;
+            }
+
+            if (trackEagerly) {
+                float delta = abs(getTranslation() - translation);
+                if (delta > mTouchSlop) {
+                    mMagneticAnimator.animateToFinalPosition(translation);
+                } else {
+                    mMagneticAnimator.cancel();
+                    setTranslation(translation);
+                }
+            } else {
+                mMagneticAnimator.animateToFinalPosition(translation);
             }
         }
 
@@ -183,6 +199,7 @@
     private void initDimens() {
         mContentShift = getResources().getDimensionPixelSize(
                 R.dimen.shelf_transform_content_shift);
+        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
index fe3a856..a9ca635 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt
@@ -62,6 +62,7 @@
 
     private val buttonShape = Path()
     // Color and style
+    private val outlineStaticColor = context.getColor(R.color.magic_action_button_stroke_color)
     private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
         val bgColor =
             context.getColor(
@@ -70,15 +71,17 @@
         color = bgColor
         style = Paint.Style.FILL
     }
-    private val outlinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
-        val outlineColor =
-            context.getColor(
-                com.android.internal.R.color.materialColorOutlineVariant
-            )
-        color = outlineColor
+    private val outlineGradientPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+        color = outlineStaticColor
         style = Paint.Style.STROKE
         strokeWidth = outlineStrokeWidth
     }
+    private val outlineSolidPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+        color = outlineStaticColor
+        style = Paint.Style.STROKE
+        strokeWidth = outlineStrokeWidth
+    }
+
     private val outlineStartColor =
         context.getColor(
             com.android.internal.R.color.materialColorTertiaryContainer
@@ -91,21 +94,35 @@
         context.getColor(
             com.android.internal.R.color.materialColorPrimary
         )
+
     // Animation
     private var gradientAnimator: ValueAnimator
     private var rotationAngle = 20f // Start rotation at 20 degrees
+    private var fadeAnimator: ValueAnimator? = null
+    private var gradientAlpha = 255    // Fading out gradient
+    private var solidAlpha = 0         // Fading in solid color
 
     init {
         gradientAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
-            duration = 5000 // 5 seconds
+            duration = 1500
             interpolator = Interpolators.LINEAR
-            repeatCount = 1
+            repeatCount = 0
             addUpdateListener { animator ->
                 val animatedValue = animator.animatedValue as Float
                 rotationAngle = 20f + animatedValue * 360f // Rotate in a spiral
                 invalidateSelf()
             }
-            // TODO: Reset the outline color when animation ends.
+            start()
+        }
+        fadeAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = 500
+            startDelay = 1000
+            addUpdateListener { animator ->
+                val progress = animator.animatedValue as Float
+                gradientAlpha = ((1 - progress) * 255).toInt()  // Fade out gradient
+                solidAlpha = (progress * 255).toInt()          // Fade in color
+                invalidateSelf()
+            }
             start()
         }
     }
@@ -120,14 +137,9 @@
         // Draw background
         canvas.clipPath(buttonShape)
         canvas.drawPath(buttonShape, bgPaint)
-        // Apply gradient to outline
-        canvas.drawPath(buttonShape, outlinePaint)
-        updateGradient(boundsF)
-        canvas.restore()
-    }
 
-    private fun updateGradient(boundsF: RectF) {
-        val gradient = LinearGradient(
+        // Set up outline gradient
+        val gradientShader = LinearGradient(
             boundsF.left, boundsF.top,
             boundsF.right, boundsF.bottom,
             intArrayOf(outlineStartColor, outlineMiddleColor, outlineEndColor),
@@ -137,9 +149,17 @@
         // Create a rotation matrix for the spiral effect
         val matrix = Matrix()
         matrix.setRotate(rotationAngle, boundsF.centerX(), boundsF.centerY())
-        gradient.setLocalMatrix(matrix)
+        gradientShader.setLocalMatrix(matrix)
 
-        outlinePaint.shader = gradient
+        // Apply gradient to outline
+        outlineGradientPaint.shader = gradientShader
+        outlineGradientPaint.alpha = gradientAlpha
+        canvas.drawPath(buttonShape, outlineGradientPaint)
+        // Apply solid color to outline
+        outlineSolidPaint.alpha = solidAlpha
+        canvas.drawPath(buttonShape, outlineSolidPaint)
+
+        canvas.restore()
     }
 
     override fun onBoundsChange(bounds: Rect) {
@@ -149,13 +169,15 @@
 
     override fun setAlpha(alpha: Int) {
         bgPaint.alpha = alpha
-        outlinePaint.alpha = alpha
+        outlineGradientPaint.alpha = alpha
+        outlineSolidPaint.alpha = alpha
         invalidateSelf()
     }
 
     override fun setColorFilter(colorFilter: ColorFilter?) {
         bgPaint.colorFilter = colorFilter
-        outlinePaint.colorFilter = colorFilter
+        outlineGradientPaint.colorFilter = colorFilter
+        outlineSolidPaint.colorFilter = colorFilter
         invalidateSelf()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 26d318b..488aa44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1604,12 +1604,15 @@
         }
 
         if (shouldShowBubbleButton(entry)) {
+            boolean isBubble = NotificationBundleUi.isEnabled()
+                    ? mContainingNotification.getEntryAdapter().isBubble()
+                    : entry.isBubble();
             // explicitly resolve drawable resource using SystemUI's theme
-            Drawable d = mContext.getDrawable(entry.isBubble()
+            Drawable d = mContext.getDrawable(isBubble
                     ? com.android.wm.shell.R.drawable.bubble_ic_stop_bubble
                     : com.android.wm.shell.R.drawable.bubble_ic_create_bubble);
 
-            String contentDescription = mContext.getResources().getString(entry.isBubble()
+            String contentDescription = mContext.getResources().getString(isBubble
                     ? R.string.notification_conversation_unbubble
                     : R.string.notification_conversation_bubble);
 
@@ -1652,12 +1655,18 @@
 
     @VisibleForTesting
     boolean shouldShowBubbleButton(NotificationEntry entry) {
-        boolean isPersonWithShortcut =
-                mPeopleIdentifier.getPeopleNotificationType(entry)
-                        >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
+        int peopleType = NotificationBundleUi.isEnabled()
+                ? mContainingNotification.getEntryAdapter().getPeopleNotificationType()
+                : mPeopleIdentifier.getPeopleNotificationType(entry);
+        Notification.BubbleMetadata bubbleMetadata = NotificationBundleUi.isEnabled()
+                ? mContainingNotification.getEntryAdapter().getSbn().getNotification()
+                        .getBubbleMetadata()
+                : entry.getBubbleMetadata();
+        boolean isPersonWithShortcut = peopleType
+                >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
         return mBubblesEnabledForUser
                 && isPersonWithShortcut
-                && entry.getBubbleMetadata() != null;
+                && bubbleMetadata != null;
     }
 
     private void applySnoozeAction(View layout) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index e89a76f..c03dc27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -46,9 +46,9 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
@@ -261,15 +261,19 @@
             mSnoozeItem = createSnoozeItem(mContext);
         }
         mFeedbackItem = createFeedbackItem(mContext);
-        NotificationEntry entry = mParent.getEntry();
-        int personNotifType = mPeopleNotificationIdentifier.getPeopleNotificationType(entry);
+        int personNotifType = NotificationBundleUi.isEnabled()
+                ? mParent.getEntryAdapter().getPeopleNotificationType()
+                : mPeopleNotificationIdentifier.getPeopleNotificationType(mParent.getEntryLegacy());
+        StatusBarNotification sbn = NotificationBundleUi.isEnabled()
+                ? mParent.getEntryAdapter().getSbn()
+                : mParent.getEntryLegacy().getSbn();
         if (personNotifType == PeopleNotificationIdentifier.TYPE_PERSON) {
             mInfoItem = createPartialConversationItem(mContext);
         } else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) {
             mInfoItem = createConversationItem(mContext);
         } else if (android.app.Flags.uiRichOngoing()
                 && Flags.permissionHelperUiRichOngoing()
-                && entry.getSbn().getNotification().isPromotedOngoing()) {
+                && sbn.getNotification().isPromotedOngoing()) {
             mInfoItem = createPromotedItem(mContext);
         } else {
             mInfoItem = createInfoItem(mContext);
@@ -358,7 +362,9 @@
             final float dismissThreshold = getDismissThreshold();
             final boolean snappingToDismiss = delta < -dismissThreshold || delta > dismissThreshold;
             if (mSnappingToDismiss != snappingToDismiss) {
-                getMenuView().performHapticFeedback(CLOCK_TICK);
+                if (!Flags.magneticNotificationSwipes()) {
+                    getMenuView().performHapticFeedback(CLOCK_TICK);
+                }
             }
             mSnappingToDismiss = snappingToDismiss;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
index 08c1d71..03990bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
@@ -87,9 +87,15 @@
                 // It's not a system app at all.
                 return false
             } else {
-                // If there's no launch intent, it's probably a headless app.
-                val pm = context.packageManager
-                return (pm.getLaunchIntentForPackage(info.packageName) == null)
+                // If there's no launch intent, it's probably a headless app. Check for both
+                // direct-aware and -unaware intents; otherwise this will almost certainly fail
+                // for notifications posted before unlocking.
+                val packageLaunchIntent =
+                    context.packageManager.getLaunchIntentForPackage(
+                        info.packageName,
+                        /* includeDirectBootUnaware= */ true,
+                    )
+                return packageLaunchIntent == null
             }
         } else {
             // If for some reason we don't have the app info, we don't know; best assume it's
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt
index aa69517..48cff74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt
@@ -33,12 +33,12 @@
 interface MagneticNotificationRowManager {
 
     /**
-     * Set the swipe threshold in pixels. After crossing the threshold, the magnetic target detaches
-     * and the magnetic neighbors snap back.
+     * Notifies a change in the device density. The density can be used to compute the values of
+     * thresholds in pixels.
      *
-     * @param[threshold] Swipe threshold in pixels.
+     * @param[density] The device density.
      */
-    fun setSwipeThresholdPx(thresholdPx: Float)
+    fun onDensityChange(density: Float)
 
     /**
      * Set the magnetic and roundable targets of a magnetic swipe interaction.
@@ -87,6 +87,9 @@
      */
     fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float? = null)
 
+    /** Determine if the given [ExpandableNotificationRow] has been magnetically detached. */
+    fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean
+
     /* Reset any roundness that magnetic targets may have */
     fun resetRoundness()
 
@@ -104,12 +107,15 @@
         /** Detaching threshold in dp */
         const val MAGNETIC_DETACH_THRESHOLD_DP = 56
 
+        /** Re-attaching threshold in dp */
+        const val MAGNETIC_ATTACH_THRESHOLD_DP = 40
+
         /* An empty implementation of a manager */
         @JvmStatic
         val Empty: MagneticNotificationRowManager
             get() =
                 object : MagneticNotificationRowManager {
-                    override fun setSwipeThresholdPx(thresholdPx: Float) {}
+                    override fun onDensityChange(density: Float) {}
 
                     override fun setMagneticAndRoundableTargets(
                         swipingRow: ExpandableNotificationRow,
@@ -127,6 +133,10 @@
                         velocity: Float?,
                     ) {}
 
+                    override fun isMagneticRowSwipeDetached(
+                        row: ExpandableNotificationRow
+                    ): Boolean = false
+
                     override fun resetRoundness() {}
 
                     override fun reset() {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
index 5a23f7c..6e8b222 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
@@ -47,6 +47,7 @@
         private set
 
     private var magneticDetachThreshold = Float.POSITIVE_INFINITY
+    private var magneticAttachThreshold = 0f
 
     // Has the roundable target been set for the magnetic view that is being swiped.
     val isSwipedViewRoundableSet: Boolean
@@ -57,13 +58,25 @@
         SpringForce().setStiffness(DETACH_STIFFNESS).setDampingRatio(DETACH_DAMPING_RATIO)
     private val snapForce =
         SpringForce().setStiffness(SNAP_BACK_STIFFNESS).setDampingRatio(SNAP_BACK_DAMPING_RATIO)
+    private val attachForce =
+        SpringForce().setStiffness(ATTACH_STIFFNESS).setDampingRatio(ATTACH_DAMPING_RATIO)
 
     // Multiplier applied to the translation of a row while swiped
     val swipedRowMultiplier =
         MAGNETIC_TRANSLATION_MULTIPLIERS[MAGNETIC_TRANSLATION_MULTIPLIERS.size / 2]
 
-    override fun setSwipeThresholdPx(thresholdPx: Float) {
-        magneticDetachThreshold = thresholdPx
+    /**
+     * An offset applied to input translation that increases on subsequent re-attachments of a
+     * detached magnetic view. This helps keep computations consistent when the drag gesture input
+     * and the swiped notification don't share the same origin point after a re-attaching animation.
+     */
+    private var translationOffset = 0f
+
+    override fun onDensityChange(density: Float) {
+        magneticDetachThreshold =
+            density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+        magneticAttachThreshold =
+            density * MagneticNotificationRowManager.MAGNETIC_ATTACH_THRESHOLD_DP
     }
 
     override fun setMagneticAndRoundableTargets(
@@ -72,6 +85,7 @@
         sectionsManager: NotificationSectionsManager,
     ) {
         if (currentState == State.IDLE) {
+            translationOffset = 0f
             updateMagneticAndRoundableTargets(swipingRow, stackScrollLayout, sectionsManager)
             currentState = State.TARGETS_SET
         } else {
@@ -121,36 +135,36 @@
 
         val canTargetBeDismissed =
             currentMagneticListeners.swipedListener()?.canRowBeDismissed() ?: false
+        val correctedTranslation = translation - translationOffset
         when (currentState) {
             State.IDLE -> {
                 logger.logMagneticRowTranslationNotSet(currentState, row.getLoggingKey())
                 return false
             }
             State.TARGETS_SET -> {
-                pullTargets(translation, canTargetBeDismissed)
+                pullTargets(correctedTranslation, canTargetBeDismissed)
                 currentState = State.PULLING
             }
             State.PULLING -> {
-                updateRoundness(translation)
+                updateRoundness(correctedTranslation)
                 if (canTargetBeDismissed) {
-                    pullDismissibleRow(translation)
+                    pullDismissibleRow(correctedTranslation)
                 } else {
-                    pullTargets(translation, canSwipedBeDismissed = false)
+                    pullTargets(correctedTranslation, canSwipedBeDismissed = false)
                 }
             }
             State.DETACHED -> {
-                val swiped = currentMagneticListeners.swipedListener()
-                swiped?.setMagneticTranslation(translation)
+                translateDetachedRow(correctedTranslation)
             }
         }
         return true
     }
 
-    private fun updateRoundness(translation: Float) {
+    private fun updateRoundness(translation: Float, animate: Boolean = false) {
         val normalizedTranslation = abs(swipedRowMultiplier * translation) / magneticDetachThreshold
         notificationRoundnessManager.setRoundnessForAffectedViews(
             /* roundness */ normalizedTranslation.coerceIn(0f, MAX_PRE_DETACH_ROUNDNESS),
-            /* animate */ false,
+            animate,
         )
     }
 
@@ -232,7 +246,28 @@
         )
     }
 
+    private fun translateDetachedRow(translation: Float) {
+        val targetTranslation = swipedRowMultiplier * translation
+        val crossedThreshold = abs(targetTranslation) <= magneticAttachThreshold
+        if (crossedThreshold) {
+            translationOffset += translation
+            updateRoundness(translation = 0f, animate = true)
+            currentMagneticListeners.swipedListener()?.let { attach(it) }
+            currentState = State.PULLING
+        } else {
+            val swiped = currentMagneticListeners.swipedListener()
+            swiped?.setMagneticTranslation(translation, trackEagerly = false)
+        }
+    }
+
+    private fun attach(listener: MagneticRowListener) {
+        listener.cancelMagneticAnimations()
+        listener.triggerMagneticForce(endTranslation = 0f, attachForce)
+        msdlPlayer.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR)
+    }
+
     override fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float?) {
+        translationOffset = 0f
         if (row.isSwipedTarget()) {
             when (currentState) {
                 State.PULLING -> {
@@ -254,9 +289,13 @@
         }
     }
 
+    override fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean =
+        row.isSwipedTarget() && currentState == State.DETACHED
+
     override fun resetRoundness() = notificationRoundnessManager.clear()
 
     override fun reset() {
+        translationOffset = 0f
         currentMagneticListeners.forEach {
             it?.cancelMagneticAnimations()
             it?.cancelTranslationAnimations()
@@ -300,6 +339,8 @@
         private const val DETACH_DAMPING_RATIO = 0.95f
         private const val SNAP_BACK_STIFFNESS = 550f
         private const val SNAP_BACK_DAMPING_RATIO = 0.6f
+        private const val ATTACH_STIFFNESS = 800f
+        private const val ATTACH_DAMPING_RATIO = 0.95f
 
         // Maximum value of corner roundness that gets applied during the pre-detach dragging
         private const val MAX_PRE_DETACH_ROUNDNESS = 0.8f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt
index 5959ef1..344dab4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt
@@ -21,8 +21,17 @@
 /** A listener that responds to magnetic forces applied to an [ExpandableNotificationRow] */
 interface MagneticRowListener {
 
-    /** Set a translation due to a magnetic attachment. */
-    fun setMagneticTranslation(translation: Float)
+    /**
+     * Set a translation due to a magnetic attachment.
+     *
+     * If a magnetic animation is running, [trackEagerly] decides if the new translation is applied
+     * immediately or if the animation finishes first. When applying the translation immediately,
+     * the change in translation must be greater than a touch slop threshold.
+     *
+     * @param[translation] Incoming gesture translation.
+     * @param[trackEagerly] Whether we eagerly track the incoming translation or not.
+     */
+    fun setMagneticTranslation(translation: Float, trackEagerly: Boolean = true)
 
     /**
      * Trigger the magnetic behavior when the row detaches or snaps back from its magnetic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index f3d8ee2..612c19f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -486,15 +486,22 @@
                 }
 
                 @Override
+                public boolean isMagneticViewDetached(View view) {
+                    if (view instanceof ExpandableNotificationRow row) {
+                        return mMagneticNotificationRowManager.isMagneticRowSwipeDetached(row);
+                    } else {
+                        return false;
+                    }
+                }
+
+                @Override
                 public float getTotalTranslationLength(View animView) {
                     return mView.getTotalTranslationLength(animView);
                 }
 
                 @Override
                 public void onDensityScaleChange(float density) {
-                    mMagneticNotificationRowManager.setSwipeThresholdPx(
-                            density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
-                    );
+                    mMagneticNotificationRowManager.onDensityChange(density);
                 }
 
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index c5a846e..5105e55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -255,12 +255,13 @@
         int menuSnapTarget = menuRow.getMenuSnapTarget();
         boolean isNonFalseMenuRevealingGesture =
                 isMenuRevealingGestureAwayFromMenu && !isFalseGesture();
+        boolean isMagneticViewDetached = mCallback.isMagneticViewDetached(animView);
         if ((isNonDismissGestureTowardsMenu || isNonFalseMenuRevealingGesture)
                 && menuSnapTarget != 0) {
             // Menu has not been snapped to previously and this is menu revealing gesture
             snapOpen(animView, menuSnapTarget, velocity);
             menuRow.onSnapOpen();
-        } else if (isDismissGesture && !gestureTowardsMenu) {
+        } else if (isDismissGesture && (!gestureTowardsMenu || isMagneticViewDetached)) {
             dismiss(animView, velocity);
             menuRow.onDismiss();
         } else {
@@ -272,6 +273,7 @@
     private void handleSwipeFromOpenState(MotionEvent ev, View animView, float velocity,
             NotificationMenuRowPlugin menuRow) {
         boolean isDismissGesture = isDismissGesture(ev);
+        boolean isMagneticViewDetached = mCallback.isMagneticViewDetached(animView);
 
         final boolean withinSnapMenuThreshold =
                 menuRow.isWithinSnapMenuThreshold();
@@ -280,7 +282,7 @@
             // Haven't moved enough to unsnap from the menu
             menuRow.onSnapOpen();
             snapOpen(animView, menuRow.getMenuSnapTarget(), velocity);
-        } else if (isDismissGesture && !menuRow.shouldSnapBack()) {
+        } else if (isDismissGesture && (!menuRow.shouldSnapBack() || isMagneticViewDetached)) {
             // Only dismiss if we're not moving towards the menu
             dismiss(animView, velocity);
             menuRow.onDismiss();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index a4ee4ad..9d9f01b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -31,11 +31,11 @@
 
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 import static com.android.systemui.Flags.keyboardShortcutHelperRewrite;
-import static com.android.systemui.Flags.lightRevealMigration;
 import static com.android.systemui.Flags.relockWithPowerButtonImmediately;
 import static com.android.systemui.Flags.statusBarSignalPolicyRefactor;
 import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
 import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
+import static com.android.systemui.shared.Flags.ambientAod;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 
 import android.annotation.Nullable;
@@ -980,7 +980,7 @@
 
             @Override
             public void onKeyguardGoingAwayChanged() {
-                if (lightRevealMigration()) {
+                if (ambientAod()) {
                     // This code path is not used if the KeyguardTransitionRepository is managing
                     // the lightreveal scrim.
                     return;
@@ -2446,7 +2446,7 @@
             return;
         }
 
-        if (lightRevealMigration()) {
+        if (ambientAod()) {
             return;
         }
 
@@ -3103,7 +3103,7 @@
 
                 @Override
                 public void onDozeAmountChanged(float linear, float eased) {
-                    if (!lightRevealMigration()
+                    if (!ambientAod()
                             && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
                         // If wakeAndUnlocking, this is handled in AuthRippleInteractor
                         if (!mBiometricUnlockController.isWakeAndUnlock()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 36193bd..3c14462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -49,6 +49,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.logging.KeyguardLogger;
+import com.android.systemui.Flags;
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -475,12 +476,14 @@
                 UserHandle.USER_ALL);
         updateUserSwitcher();
         onThemeChanged();
-        collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer,
-                mCoroutineDispatcher);
-        collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(),
-                mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
-        collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(),
-                mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+        if (!Flags.glanceableHubV2()) {
+            collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer,
+                    mCoroutineDispatcher);
+            collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(),
+                    mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+            collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(),
+                    mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+        }
         if (NewStatusBarIcons.isEnabled()) {
             ComposeView batteryComposeView = new ComposeView(mContext);
             UnifiedBatteryViewBinder.bind(
@@ -645,7 +648,7 @@
                         && !mDozing
                         && !hideForBypass
                         && !mDisableStateTracker.isDisabled()
-                        && (!mCommunalShowing || mExplicitAlpha != -1)
+                        && (Flags.glanceableHubV2() || (!mCommunalShowing || mExplicitAlpha != -1))
                         ? View.VISIBLE : View.INVISIBLE;
 
         updateViewState(newAlpha, newVisibility);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 0d43789..8890db3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -18,7 +18,6 @@
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
 import com.android.systemui.DejankUtils
-import com.android.systemui.Flags.lightRevealMigration
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.KeyguardViewMediator
@@ -26,6 +25,7 @@
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
+import com.android.systemui.shared.Flags.ambientAod
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -100,7 +100,7 @@
             duration = LIGHT_REVEAL_ANIMATION_DURATION
             interpolator = Interpolators.LINEAR
             addUpdateListener {
-                if (lightRevealMigration()) return@addUpdateListener
+                if (ambientAod()) return@addUpdateListener
                 if (lightRevealScrim.revealEffect !is CircleReveal) {
                     lightRevealScrim.revealAmount = it.animatedValue as Float
                 }
@@ -116,7 +116,7 @@
             addListener(
                 object : AnimatorListenerAdapter() {
                     override fun onAnimationCancel(animation: Animator) {
-                        if (lightRevealMigration()) return
+                        if (ambientAod()) return
                         if (lightRevealScrim.revealEffect !is CircleReveal) {
                             lightRevealScrim.revealAmount = 1f
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
index 1a8ca95..f4afc24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.kairos.BuildSpec
 import com.android.systemui.kairos.ExperimentalKairosApi
 import com.android.systemui.kairos.State
+import com.android.systemui.kairos.combine
 import com.android.systemui.kairos.flatMap
 import com.android.systemui.kairosBuilder
 import com.android.systemui.log.table.TableLogBuffer
@@ -55,9 +56,15 @@
     @Assisted private val isCarrierMerged: State<Boolean>,
 ) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() {
 
+    private var dumpCache: DumpCache? = null
+
     init {
         onActivated {
             logDiffsForTable(isCarrierMerged, tableLogBuffer, columnName = "isCarrierMerged")
+            combine(isCarrierMerged, activeRepo) { isCarrierMerged, activeRepo ->
+                    DumpCache(isCarrierMerged, activeRepo)
+                }
+                .observe { dumpCache = it }
         }
     }
 
@@ -198,13 +205,6 @@
 
     override val isInEcmMode: State<Boolean> = activeRepo.flatMap { it.isInEcmMode }
 
-    private var dumpCache: DumpCache? = null
-
-    private data class DumpCache(
-        val isCarrierMerged: Boolean,
-        val activeRepo: MobileConnectionRepositoryKairos,
-    )
-
     fun dump(pw: PrintWriter) {
         val cache = dumpCache ?: return
         val ipw = IndentingPrintWriter(pw, "  ")
@@ -227,6 +227,11 @@
         ipw.decreaseIndent()
     }
 
+    private data class DumpCache(
+        val isCarrierMerged: Boolean,
+        val activeRepo: MobileConnectionRepositoryKairos,
+    )
+
     @AssistedFactory
     interface Factory {
         fun create(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
index e468159..e6c2921 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
@@ -131,6 +131,8 @@
     private val mobileRepoFactory: Lazy<ConnectionRepoFactory>,
 ) : MobileConnectionsRepositoryKairos, Dumpable, KairosBuilder by kairosBuilder() {
 
+    private var dumpCache: DumpCache? = null
+
     init {
         dumpManager.registerNormalDumpable("MobileConnectionsRepositoryKairos", this)
     }
@@ -253,6 +255,7 @@
                 .asIncremental()
                 .mapValues { (subId, sub) -> mobileRepoFactory.get().create(subId) }
                 .applyLatestSpecForKey()
+                .apply { observe { dumpCache = DumpCache(it) } }
         }
 
     private val telephonyManagerState: State<Pair<Int?, Set<Int>>> = buildState {
@@ -479,10 +482,6 @@
             profileClass = profileClass,
         )
 
-    private var dumpCache: DumpCache? = null
-
-    private data class DumpCache(val repos: Map<Int, FullMobileConnectionRepositoryKairos>)
-
     override fun dump(pw: PrintWriter, args: Array<String>) {
         val cache = dumpCache ?: return
         val ipw = IndentingPrintWriter(pw, " ")
@@ -494,10 +493,16 @@
 
         ipw.println("Connections (${cache.repos.size} total):")
         ipw.increaseIndent()
-        cache.repos.values.forEach { it.dump(ipw) }
+        cache.repos.values.forEach {
+            if (it is FullMobileConnectionRepositoryKairos) {
+                it.dump(ipw)
+            }
+        }
         ipw.decreaseIndent()
     }
 
+    private data class DumpCache(val repos: Map<Int, MobileConnectionRepositoryKairos>)
+
     fun interface ConnectionRepoFactory {
         fun create(subId: Int): BuildSpec<MobileConnectionRepositoryKairos>
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 0eabb4ec..af4e61a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -61,31 +61,31 @@
      * consider this connection to be serving data, and thus want to show a network type icon, when
      * data is connected. Other data connection states would typically cause us not to show the icon
      */
-    val isDataConnected: StateFlow<Boolean>
+    val isDataConnected: Flow<Boolean>
 
     /** True if we consider this connection to be in service, i.e. can make calls */
-    val isInService: StateFlow<Boolean>
+    val isInService: Flow<Boolean>
 
     /** True if this connection is emergency only */
-    val isEmergencyOnly: StateFlow<Boolean>
+    val isEmergencyOnly: Flow<Boolean>
 
     /** Observable for the data enabled state of this connection */
-    val isDataEnabled: StateFlow<Boolean>
+    val isDataEnabled: Flow<Boolean>
 
     /** True if the RAT icon should always be displayed and false otherwise. */
-    val alwaysShowDataRatIcon: StateFlow<Boolean>
+    val alwaysShowDataRatIcon: Flow<Boolean>
 
     /** Canonical representation of the current mobile signal strength as a triangle. */
-    val signalLevelIcon: StateFlow<SignalIconModel>
+    val signalLevelIcon: Flow<SignalIconModel>
 
     /** Observable for RAT type (network type) indicator */
-    val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>
+    val networkTypeIconGroup: Flow<NetworkTypeIconModel>
 
     /** Whether or not to show the slice attribution */
-    val showSliceAttribution: StateFlow<Boolean>
+    val showSliceAttribution: Flow<Boolean>
 
     /** True if this connection is satellite-based */
-    val isNonTerrestrial: StateFlow<Boolean>
+    val isNonTerrestrial: Flow<Boolean>
 
     /**
      * Provider name for this network connection. The name can be one of 3 values:
@@ -95,7 +95,7 @@
      *    override in [connectionInfo.operatorAlphaShort], a value that is derived from
      *    [ServiceState]
      */
-    val networkName: StateFlow<NetworkNameModel>
+    val networkName: Flow<NetworkNameModel>
 
     /**
      * Provider name for this network connection. The name can be one of 3 values:
@@ -108,26 +108,26 @@
      * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data
      *   provided is identical
      */
-    val carrierName: StateFlow<String>
+    val carrierName: Flow<String>
 
     /** True if there is only one active subscription. */
-    val isSingleCarrier: StateFlow<Boolean>
+    val isSingleCarrier: Flow<Boolean>
 
     /**
      * True if this connection is considered roaming. The roaming bit can come from [ServiceState],
      * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a
      * connection to be roaming while carrier network change is active
      */
-    val isRoaming: StateFlow<Boolean>
+    val isRoaming: Flow<Boolean>
 
     /** See [MobileIconsInteractor.isForceHidden]. */
     val isForceHidden: Flow<Boolean>
 
     /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */
-    val isAllowedDuringAirplaneMode: StateFlow<Boolean>
+    val isAllowedDuringAirplaneMode: Flow<Boolean>
 
     /** True when in carrier network change mode */
-    val carrierNetworkChangeActive: StateFlow<Boolean>
+    val carrierNetworkChangeActive: Flow<Boolean>
 }
 
 /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt
index 87877b3..6b9c537 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt
@@ -32,11 +32,20 @@
 import com.android.systemui.kairos.mapValues
 import com.android.systemui.kairos.toColdConflatedFlow
 import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import dagger.Provides
 import dagger.multibindings.ElementsIntoSet
 import javax.inject.Inject
@@ -45,6 +54,8 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 @ExperimentalKairosApi
@@ -60,6 +71,7 @@
     context: Context,
     mobileMappingsProxy: MobileMappingsProxy,
     private val userSetupRepo: UserSetupRepository,
+    private val logFactory: TableLogBufferFactory,
 ) : MobileIconsInteractor, KairosBuilder by kairosBuilder() {
 
     private val interactorsBySubIdK = buildIncremental {
@@ -158,7 +170,37 @@
         get() = repo.isDeviceEmergencyCallCapable
 
     override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
-        interactorsBySubId.value[subId] ?: error("Unknown subscription id: $subId")
+        object : MobileIconInteractor {
+            override val tableLogBuffer: TableLogBuffer =
+                logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE)
+            override val activity: Flow<DataActivityModel> = latest { activity }
+            override val mobileIsDefault: Flow<Boolean> = latest { mobileIsDefault }
+            override val isDataConnected: Flow<Boolean> = latest { isDataConnected }
+            override val isInService: Flow<Boolean> = latest { isInService }
+            override val isEmergencyOnly: Flow<Boolean> = latest { isEmergencyOnly }
+            override val isDataEnabled: Flow<Boolean> = latest { isDataEnabled }
+            override val alwaysShowDataRatIcon: Flow<Boolean> = latest { alwaysShowDataRatIcon }
+            override val signalLevelIcon: Flow<SignalIconModel> = latest { signalLevelIcon }
+            override val networkTypeIconGroup: Flow<NetworkTypeIconModel> = latest {
+                networkTypeIconGroup
+            }
+            override val showSliceAttribution: Flow<Boolean> = latest { showSliceAttribution }
+            override val isNonTerrestrial: Flow<Boolean> = latest { isNonTerrestrial }
+            override val networkName: Flow<NetworkNameModel> = latest { networkName }
+            override val carrierName: Flow<String> = latest { carrierName }
+            override val isSingleCarrier: Flow<Boolean> = latest { isSingleCarrier }
+            override val isRoaming: Flow<Boolean> = latest { isRoaming }
+            override val isForceHidden: Flow<Boolean> = latest { isForceHidden }
+            override val isAllowedDuringAirplaneMode: Flow<Boolean> = latest {
+                isAllowedDuringAirplaneMode
+            }
+            override val carrierNetworkChangeActive: Flow<Boolean> = latest {
+                carrierNetworkChangeActive
+            }
+
+            private fun <T> latest(block: MobileIconInteractor.() -> Flow<T>): Flow<T> =
+                interactorsBySubId.flatMapLatestConflated { it[subId]?.block() ?: emptyFlow() }
+        }
 
     @dagger.Module
     object Module {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 9a81992..7f778bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -256,6 +256,9 @@
                     if (mClockFormat != null) {
                         mClockFormat.setTimeZone(mCalendar.getTimeZone());
                     }
+                    if (mContentDescriptionFormat != null) {
+                        mContentDescriptionFormat.setTimeZone(mCalendar.getTimeZone());
+                    }
                 });
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
                 final Locale newLocale = getResources().getConfiguration().locale;
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 05b2e0d..b33eafc 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -414,16 +414,15 @@
         }
     }
 
+    private suspend fun SelectedUserModel.isEligibleForLogout(): Boolean {
+        return withContext(backgroundDispatcher) {
+            selectionStatus == SelectionStatus.SELECTION_COMPLETE &&
+                devicePolicyManager.logoutUser != null
+        }
+    }
+
     companion object {
         private const val TAG = "UserRepository"
         @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
     }
 }
-
-fun SelectedUserModel.isEligibleForLogout(): Boolean {
-    // TODO(b/206032495): should call mDevicePolicyManager.getLogoutUserId() instead of
-    // hardcode it to USER_SYSTEM so it properly supports headless system user mode
-    // (and then call mDevicePolicyManager.clearLogoutUser() after switched)
-    return selectionStatus == SelectionStatus.SELECTION_COMPLETE &&
-        userInfo.id != android.os.UserHandle.USER_SYSTEM
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/AlarmTimeout.java b/packages/SystemUI/src/com/android/systemui/util/AlarmTimeout.java
index a791376..38ac5e6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/AlarmTimeout.java
+++ b/packages/SystemUI/src/com/android/systemui/util/AlarmTimeout.java
@@ -26,7 +26,6 @@
  */
 public class AlarmTimeout implements AlarmManager.OnAlarmListener {
 
-    public static final int MODE_CRASH_IF_SCHEDULED = 0;
     public static final int MODE_IGNORE_IF_SCHEDULED = 1;
     public static final int MODE_RESCHEDULE_IF_SCHEDULED = 2;
 
@@ -48,17 +47,11 @@
      * Schedules an alarm in {@code timeout} milliseconds in the future.
      *
      * @param timeout How long to wait from now.
-     * @param mode {@link #MODE_CRASH_IF_SCHEDULED}, {@link #MODE_IGNORE_IF_SCHEDULED} or
-     *             {@link #MODE_RESCHEDULE_IF_SCHEDULED}.
+     * @param mode {@link #MODE_IGNORE_IF_SCHEDULED} or {@link #MODE_RESCHEDULE_IF_SCHEDULED}.
      * @return {@code true} when scheduled successfully, {@code false} otherwise.
      */
     public boolean schedule(long timeout, int mode) {
         switch (mode) {
-            case MODE_CRASH_IF_SCHEDULED:
-                if (mScheduled) {
-                    throw new IllegalStateException(mTag + " timeout is already scheduled");
-                }
-                break;
             case MODE_IGNORE_IF_SCHEDULED:
                 if (mScheduled) {
                     return false;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index 43d1ef4..32f784f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.volume.dialog.sliders.ui
 
-import android.graphics.drawable.Drawable
 import android.view.View
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.core.tween
@@ -29,7 +28,6 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.SliderDefaults
 import androidx.compose.runtime.Composable
@@ -43,7 +41,8 @@
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.theme.PlatformTheme
-import com.android.compose.ui.graphics.painter.DrawablePainter
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.res.R
@@ -155,7 +154,7 @@
                 },
             )
         },
-        accessibilityParams = AccessibilityParams(label = sliderStateModel.label),
+        accessibilityParams = AccessibilityParams(contentDescription = sliderStateModel.label),
         modifier =
             modifier.pointerInput(Unit) {
                 coroutineScope {
@@ -172,7 +171,7 @@
 
 @Composable
 private fun BoxScope.VolumeIcon(
-    drawable: Drawable,
+    icon: Icon.Loaded,
     isVisible: Boolean,
     modifier: Modifier = Modifier,
 ) {
@@ -182,6 +181,6 @@
         exit = fadeOut(animationSpec = tween(durationMillis = 50)),
         modifier = modifier.align(Alignment.Center).size(40.dp).padding(10.dp),
     ) {
-        Icon(painter = DrawablePainter(drawable), contentDescription = null)
+        Icon(icon)
     }
 }
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 ef147c7..3712276 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
@@ -18,32 +18,36 @@
 
 import android.annotation.SuppressLint
 import android.content.Context
-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
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.withContext
 
 @SuppressLint("UseCompatLoadingForDrawables")
 class VolumeDialogSliderIconProvider
 @Inject
 constructor(
     private val context: Context,
+    @UiBackground private val uiBackgroundContext: CoroutineContext,
     private val zenModeInteractor: ZenModeInteractor,
     private val audioVolumeInteractor: AudioVolumeInteractor,
 ) {
 
-    fun getAudioSharingIcon(isMuted: Boolean): Flow<Drawable> {
+    fun getAudioSharingIcon(isMuted: Boolean): Flow<Icon.Loaded> {
         return flow {
             val iconRes =
                 if (isMuted) {
@@ -51,11 +55,12 @@
                 } else {
                     R.drawable.ic_volume_media_bt
                 }
-            emit(context.getDrawable(iconRes)!!)
+            val drawable = withContext(uiBackgroundContext) { context.getDrawable(iconRes)!! }
+            emit(Icon.Loaded(drawable = drawable, contentDescription = null, res = iconRes))
         }
     }
 
-    fun getCastIcon(isMuted: Boolean): Flow<Drawable> {
+    fun getCastIcon(isMuted: Boolean): Flow<Icon.Loaded> {
         return flow {
             val iconRes =
                 if (isMuted) {
@@ -63,7 +68,8 @@
                 } else {
                     SettingsR.drawable.ic_volume_remote
                 }
-            emit(context.getDrawable(iconRes)!!)
+            val drawable = withContext(uiBackgroundContext) { context.getDrawable(iconRes)!! }
+            emit(Icon.Loaded(drawable = drawable, contentDescription = null, res = iconRes))
         }
     }
 
@@ -74,15 +80,18 @@
         levelMax: Int,
         isMuted: Boolean,
         isRoutedToBluetooth: Boolean,
-    ): Flow<Drawable> {
+    ): Flow<Icon.Loaded> {
         return combine(
             zenModeInteractor.activeModesBlockingStream(stream),
             ringerModeForStream(stream),
         ) { activeModesBlockingStream, ringerMode ->
             if (activeModesBlockingStream?.mainMode?.icon != null) {
-                return@combine activeModesBlockingStream.mainMode.icon.drawable
+                Icon.Loaded(
+                    drawable = activeModesBlockingStream.mainMode.icon.drawable,
+                    contentDescription = null,
+                )
             } else {
-                context.getDrawable(
+                val iconRes =
                     getIconRes(
                         stream,
                         level,
@@ -92,7 +101,8 @@
                         isRoutedToBluetooth,
                         ringerMode,
                     )
-                )!!
+                val drawable = withContext(uiBackgroundContext) { context.getDrawable(iconRes)!! }
+                Icon.Loaded(drawable = drawable, contentDescription = null, res = iconRes)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
index 88a061f..ed59598 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.volume.dialog.sliders.ui.viewmodel
 
 import android.content.Context
-import android.graphics.drawable.Drawable
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
 import com.android.systemui.volume.dialog.shared.model.streamLabel
 
@@ -25,14 +25,14 @@
     val value: Float,
     val isDisabled: Boolean,
     val valueRange: ClosedFloatingPointRange<Float>,
-    val icon: Drawable,
+    val icon: Icon.Loaded,
     val label: String,
 )
 
 fun VolumeDialogStreamModel.toStateModel(
     context: Context,
     isDisabled: Boolean,
-    icon: Drawable,
+    icon: Icon.Loaded,
 ): VolumeDialogSliderStateModel {
     return VolumeDialogSliderStateModel(
         value = level.toFloat(),
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 f6aa189..faf0abd 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
@@ -108,11 +108,9 @@
                                     isMuted = isMuted,
                                     isRoutedToBluetooth = routedToBluetooth,
                                 )
-
                             is VolumeDialogSliderType.RemoteMediaStream -> {
                                 volumeDialogSliderIconProvider.getCastIcon(isMuted)
                             }
-
                             is VolumeDialogSliderType.AudioSharingStream -> {
                                 volumeDialogSliderIconProvider.getAudioSharingIcon(isMuted)
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt
index 3d98eba..f645267 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt
@@ -16,9 +16,11 @@
 
 package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
 
+import android.content.Context
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.Flags
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.res.R
@@ -28,6 +30,7 @@
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlin.coroutines.CoroutineContext
 import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,11 +42,14 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 class AudioSharingStreamSliderViewModel
 @AssistedInject
 constructor(
+    private val context: Context,
     @Assisted private val coroutineScope: CoroutineScope,
+    @UiBackground private val uiBackgroundContext: CoroutineContext,
     private val audioSharingInteractor: AudioSharingInteractor,
     private val uiEventLogger: UiEventLogger,
     private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
@@ -51,6 +57,12 @@
 ) : SliderViewModel {
     private val volumeChanges = MutableStateFlow<Int?>(null)
 
+    private val audioSharingIcon =
+        Icon.Loaded(
+            drawable = context.getDrawable(R.drawable.ic_volume_media_bt)!!,
+            contentDescription = null,
+            res = R.drawable.ic_volume_media_bt,
+        )
     override val slider: StateFlow<SliderState> =
         combine(
                 audioSharingInteractor.volume.distinctUntilChanged().onEach {
@@ -62,16 +74,17 @@
                 if (volume == null) {
                     SliderState.Empty
                 } else {
-
-                    State(
-                        value = volume.toFloat(),
-                        valueRange =
-                            audioSharingInteractor.volumeMin.toFloat()..audioSharingInteractor
-                                    .volumeMax
-                                    .toFloat(),
-                        icon = Icon.Resource(R.drawable.ic_volume_media_bt, null),
-                        label = deviceName,
-                    )
+                    withContext(uiBackgroundContext) {
+                        State(
+                            value = volume.toFloat(),
+                            valueRange =
+                                audioSharingInteractor.volumeMin.toFloat()..audioSharingInteractor
+                                        .volumeMax
+                                        .toFloat(),
+                            icon = audioSharingIcon,
+                            label = deviceName,
+                        )
+                    }
                 }
             }
             .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
@@ -107,7 +120,7 @@
     private data class State(
         override val value: Float,
         override val valueRange: ClosedFloatingPointRange<Float>,
-        override val icon: Icon,
+        override val icon: Icon.Loaded?,
         override val label: String,
     ) : SliderState {
         override val hapticFilter: SliderHapticFeedbackFilter
@@ -116,7 +129,7 @@
         override val isEnabled: Boolean
             get() = true
 
-        override val a11yStep: Float
+        override val step: Float
             get() = 1f
 
         override val disabledMessage: String?
@@ -125,6 +138,9 @@
         override val isMutable: Boolean
             get() = false
 
+        override val a11yContentDescription: String
+            get() = label
+
         override val a11yClickDescription: String?
             get() = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 9d32285..9fe0ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.media.AudioManager
 import android.util.Log
+import androidx.annotation.DrawableRes
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -28,6 +29,7 @@
 import com.android.settingslib.volume.shared.model.RingerMode
 import com.android.systemui.Flags
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.modes.shared.ModesUiIcons
@@ -40,18 +42,21 @@
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlin.coroutines.CoroutineContext
 import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 /** Models a particular slider state. */
 class AudioStreamSliderViewModel
@@ -59,10 +64,11 @@
 constructor(
     @Assisted private val audioStreamWrapper: FactoryAudioStreamWrapper,
     @Assisted private val coroutineScope: CoroutineScope,
+    @UiBackground private val uiBackgroundContext: CoroutineContext,
     private val context: Context,
     private val audioVolumeInteractor: AudioVolumeInteractor,
     private val zenModeInteractor: ZenModeInteractor,
-    private val audioSharingInteractor: AudioSharingInteractor,
+    audioSharingInteractor: AudioSharingInteractor,
     private val uiEventLogger: UiEventLogger,
     private val volumePanelLogger: VolumePanelLogger,
     private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
@@ -148,57 +154,69 @@
             null
         }
 
-    private fun AudioStreamModel.toState(
+    private suspend fun AudioStreamModel.toState(
         isEnabled: Boolean,
         ringerMode: RingerMode,
         disabledMessage: String?,
         inAudioSharing: Boolean,
         primaryDevice: CachedBluetoothDevice?,
-    ): State {
-        val label = getLabel(inAudioSharing, primaryDevice)
-        val icon = getIcon(ringerMode, inAudioSharing)
-        return State(
-            value = volume.toFloat(),
-            valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
-            hapticFilter = createHapticFilter(ringerMode),
-            icon = icon,
-            label = label,
-            disabledMessage = disabledMessage,
-            isEnabled = isEnabled,
-            a11yStep = volumeRange.step.toFloat(),
-            a11yClickDescription =
-                if (isAffectedByMute) {
-                    context.getString(
-                        if (isMuted) {
-                            R.string.volume_panel_hint_unmute
-                        } else {
-                            R.string.volume_panel_hint_mute
-                        },
-                        label,
-                    )
-                } else {
-                    null
-                },
-            a11yStateDescription =
-                if (isMuted) {
-                    context.getString(
-                        if (isAffectedByRingerMode) {
-                            if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
-                                R.string.volume_panel_hint_vibrate
+    ): State =
+        withContext(uiBackgroundContext) {
+            val label = getLabel(inAudioSharing, primaryDevice)
+            State(
+                value = volume.toFloat(),
+                valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
+                hapticFilter = createHapticFilter(ringerMode),
+                icon = getIcon(ringerMode, inAudioSharing),
+                label = label,
+                disabledMessage = disabledMessage,
+                isEnabled = isEnabled,
+                step = volumeRange.step.toFloat(),
+                a11yContentDescription =
+                    if (isEnabled) {
+                        label
+                    } else {
+                        disabledMessage?.let {
+                            context.getString(
+                                R.string.volume_slider_disabled_message_template,
+                                label,
+                                disabledMessage,
+                            )
+                        } ?: label
+                    },
+                a11yClickDescription =
+                    if (isAffectedByMute) {
+                        context.getString(
+                            if (isMuted) {
+                                R.string.volume_panel_hint_unmute
+                            } else {
+                                R.string.volume_panel_hint_mute
+                            },
+                            label,
+                        )
+                    } else {
+                        null
+                    },
+                a11yStateDescription =
+                    if (isMuted) {
+                        context.getString(
+                            if (isAffectedByRingerMode) {
+                                if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+                                    R.string.volume_panel_hint_vibrate
+                                } else {
+                                    R.string.volume_panel_hint_muted
+                                }
                             } else {
                                 R.string.volume_panel_hint_muted
                             }
-                        } else {
-                            R.string.volume_panel_hint_muted
-                        }
-                    )
-                } else {
-                    null
-                },
-            audioStreamModel = this,
-            isMutable = isAffectedByMute,
-        )
-    }
+                        )
+                    } else {
+                        null
+                    },
+                audioStreamModel = this@toState,
+                isMutable = isAffectedByMute,
+            )
+        }
 
     private fun AudioStreamModel.createHapticFilter(
         ringerMode: RingerMode
@@ -220,12 +238,14 @@
                 flowOf(context.getString(R.string.stream_notification_unavailable))
             } else {
                 if (zenModeInteractor.canBeBlockedByZenMode(audioStream)) {
-                    zenModeInteractor.activeModesBlockingStream(audioStream).map { blockingZenModes
-                        ->
-                        blockingZenModes.mainMode?.name?.let {
-                            context.getString(R.string.stream_unavailable_by_modes, it)
-                        } ?: context.getString(R.string.stream_unavailable_by_unknown)
-                    }
+                    zenModeInteractor
+                        .activeModesBlockingStream(audioStream)
+                        .map { blockingZenModes ->
+                            blockingZenModes.mainMode?.name?.let {
+                                context.getString(R.string.stream_unavailable_by_modes, it)
+                            } ?: context.getString(R.string.stream_unavailable_by_unknown)
+                        }
+                        .distinctUntilChanged()
                 } else {
                     flowOf(context.getString(R.string.stream_unavailable_by_unknown))
                 }
@@ -256,8 +276,11 @@
                 ?: error("No label for the stream: $audioStream")
         }
 
-    private fun AudioStreamModel.getIcon(ringerMode: RingerMode, inAudioSharing: Boolean): Icon {
-        val iconRes =
+    private fun AudioStreamModel.getIcon(
+        ringerMode: RingerMode,
+        inAudioSharing: Boolean,
+    ): Icon.Loaded {
+        val iconResource: Int =
             if (isMuted) {
                 if (isAffectedByRingerMode) {
                     if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
@@ -272,14 +295,21 @@
                             inAudioSharing
                     ) {
                         R.drawable.ic_volume_media_bt_mute
-                    } else R.drawable.ic_volume_off
+                    } else {
+                        R.drawable.ic_volume_off
+                    }
                 }
             } else {
                 getIconByStream(audioStream, inAudioSharing)
             }
-        return Icon.Resource(iconRes, null)
+        return Icon.Loaded(
+            drawable = context.getDrawable(iconResource)!!,
+            contentDescription = null,
+            res = iconResource,
+        )
     }
 
+    @DrawableRes
     private fun getIconByStream(audioStream: AudioStream, inAudioSharing: Boolean): Int =
         when (audioStream.value) {
             AudioManager.STREAM_MUSIC ->
@@ -302,14 +332,15 @@
     private data class State(
         override val value: Float,
         override val valueRange: ClosedFloatingPointRange<Float>,
+        override val step: Float,
         override val hapticFilter: SliderHapticFeedbackFilter,
-        override val icon: Icon,
+        override val icon: Icon.Loaded?,
         override val label: String,
         override val disabledMessage: String?,
         override val isEnabled: Boolean,
-        override val a11yStep: Float,
         override val a11yClickDescription: String?,
         override val a11yStateDescription: String?,
+        override val a11yContentDescription: String,
         override val isMutable: Boolean,
         val audioStreamModel: AudioStreamModel,
     ) : SliderState
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index a6c8091..01810f9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -21,6 +21,7 @@
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Flags
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.res.R
@@ -30,30 +31,40 @@
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlin.coroutines.CoroutineContext
 import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 class CastVolumeSliderViewModel
 @AssistedInject
 constructor(
     @Assisted private val session: MediaDeviceSession,
     @Assisted private val coroutineScope: CoroutineScope,
+    @UiBackground private val uiBackgroundContext: CoroutineContext,
     private val context: Context,
     private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
     private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
     private val volumePanelLogger: VolumePanelLogger,
 ) : SliderViewModel {
 
+    private val castLabel = context.getString(R.string.media_device_cast)
+    private val castIcon =
+        Icon.Loaded(
+            drawable = context.getDrawable(R.drawable.ic_cast)!!,
+            contentDescription = null,
+            res = R.drawable.ic_cast,
+        )
     override val slider: StateFlow<SliderState> =
         mediaDeviceSessionInteractor
             .playbackInfo(session)
             .mapNotNull {
                 volumePanelLogger.onVolumeUpdateReceived(session.sessionToken, it.currentVolume)
-                it.getCurrentState()
+                withContext(uiBackgroundContext) { it.getCurrentState() }
             }
             .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
 
@@ -83,20 +94,20 @@
         return State(
             value = currentVolume.toFloat(),
             valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
-            icon = Icon.Resource(R.drawable.ic_cast, null),
-            label = context.getString(R.string.media_device_cast),
+            icon = castIcon,
+            label = castLabel,
             isEnabled = true,
-            a11yStep = 1f,
+            step = 1f,
         )
     }
 
     private data class State(
         override val value: Float,
         override val valueRange: ClosedFloatingPointRange<Float>,
-        override val icon: Icon,
+        override val icon: Icon.Loaded?,
         override val label: String,
         override val isEnabled: Boolean,
-        override val a11yStep: Float,
+        override val step: Float,
     ) : SliderState {
         override val hapticFilter: SliderHapticFeedbackFilter
             get() = SliderHapticFeedbackFilter()
@@ -107,6 +118,9 @@
         override val isMutable: Boolean
             get() = false
 
+        override val a11yContentDescription: String
+            get() = label
+
         override val a11yClickDescription: String?
             get() = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
index 4bc237b..b1d18340 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
@@ -27,18 +27,17 @@
 sealed interface SliderState {
     val value: Float
     val valueRange: ClosedFloatingPointRange<Float>
+    val step: Float
     val hapticFilter: SliderHapticFeedbackFilter
 
-    val icon: Icon?
+    // Force preloaded icon
+    val icon: Icon.Loaded?
     val isEnabled: Boolean
     val label: String
-    /**
-     * A11y slider controls works by adjusting one step up or down. The default slider step isn't
-     * enough to trigger rounding to the correct value.
-     */
-    val a11yStep: Float
+
     val a11yClickDescription: String?
     val a11yStateDescription: String?
+    val a11yContentDescription: String
     val disabledMessage: String?
     val isMutable: Boolean
 
@@ -46,12 +45,13 @@
         override val value: Float = 0f
         override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
         override val hapticFilter = SliderHapticFeedbackFilter()
-        override val icon: Icon? = null
+        override val icon: Icon.Loaded? = null
         override val label: String = ""
         override val disabledMessage: String? = null
-        override val a11yStep: Float = 0f
+        override val step: Float = 0f
         override val a11yClickDescription: String? = null
         override val a11yStateDescription: String? = null
+        override val a11yContentDescription: String = label
         override val isEnabled: Boolean = true
         override val isMutable: Boolean = false
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt
index f6582a0..502b311 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt
@@ -40,7 +40,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.ProgressBarRangeInfo
 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
 import androidx.compose.ui.semantics.clearAndSetSemantics
@@ -52,7 +51,6 @@
 import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.res.R
 import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider
 import kotlin.math.round
 import kotlinx.coroutines.Job
@@ -108,7 +106,8 @@
         }
     }
     val semantics =
-        accessibilityParams.createSemantics(
+        createSemantics(
+            accessibilityParams,
             animatable.targetValue,
             valueRange,
             valueChange,
@@ -167,24 +166,18 @@
     return Math.round(coercedValue / stepDistance) * stepDistance
 }
 
-@Composable
-private fun AccessibilityParams.createSemantics(
+private fun createSemantics(
+    params: AccessibilityParams,
     value: Float,
     valueRange: ClosedFloatingPointRange<Float>,
     onValueChanged: (Float) -> Unit,
     isEnabled: Boolean,
     stepDistance: Float,
 ): SemanticsPropertyReceiver.() -> Unit {
-    val semanticsContentDescription =
-        disabledMessage
-            ?.takeIf { !isEnabled }
-            ?.let { message ->
-                stringResource(R.string.volume_slider_disabled_message_template, label, message)
-            } ?: label
     return {
-        contentDescription = semanticsContentDescription
+        contentDescription = params.contentDescription
         if (isEnabled) {
-            currentStateDescription?.let { stateDescription = it }
+            params.stateDescription?.let { stateDescription = it }
             progressBarRangeInfo = ProgressBarRangeInfo(value, valueRange)
         } else {
             disabled()
@@ -253,9 +246,8 @@
 }
 
 data class AccessibilityParams(
-    val label: String,
-    val currentStateDescription: String? = null,
-    val disabledMessage: String? = null,
+    val contentDescription: String,
+    val stateDescription: String? = null,
 )
 
 sealed interface Haptics {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java
index 146488b..108f3ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java
@@ -69,4 +69,25 @@
 
         assertThat(end_y).isEqualTo(end_y);
     }
+
+    @Test
+    public void avoidVerticalDisplayCutout_doesNotExceedTopBounds() {
+        final int y = DRAGGABLE_BOUNDS.top - 100;
+
+        final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout(
+                y, MENU_HEIGHT, DRAGGABLE_BOUNDS, new Rect(0, 5, 0, 6));
+
+        assertThat(end_y).isGreaterThan(DRAGGABLE_BOUNDS.top);
+    }
+
+
+    @Test
+    public void avoidVerticalDisplayCutout_doesNotExceedBottomBounds() {
+        final int y = DRAGGABLE_BOUNDS.bottom + 100;
+
+        final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout(
+                y, MENU_HEIGHT, DRAGGABLE_BOUNDS, new Rect(0, 5, 0, 6));
+
+        assertThat(end_y).isLessThan(DRAGGABLE_BOUNDS.bottom);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 8105ae0..206654a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -101,7 +101,6 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.SystemPropertiesHelper;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.log.SessionTracker;
@@ -207,7 +206,6 @@
     private @Mock ShadeWindowLogger mShadeWindowLogger;
     private @Mock SelectedUserInteractor mSelectedUserInteractor;
     private @Mock UserTracker.Callback mUserTrackerCallback;
-    private @Mock KeyguardInteractor mKeyguardInteractor;
     private @Mock KeyguardTransitionBootInteractor mKeyguardTransitionBootInteractor;
     private @Captor ArgumentCaptor<KeyguardStateController.Callback>
             mKeyguardStateControllerCallback;
@@ -1499,7 +1497,7 @@
                 mSystemPropertiesHelper,
                 () -> mock(WindowManagerLockscreenVisibilityManager.class),
                 mSelectedUserInteractor,
-                mKeyguardInteractor,
+                mKosmos.getKeyguardInteractor(),
                 mKeyguardTransitionBootInteractor,
                 mKosmos::getCommunalSceneInteractor,
                 mock(WindowManagerOcclusionManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index cf8278e..82082cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.
@@ -38,9 +38,13 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.FeedbackIcon
 import com.android.systemui.statusbar.notification.collection.EntryAdapter
+import com.android.systemui.statusbar.notification.collection.EntryAdapterFactory
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.msgStyleBubbleableFullPerson
+import com.android.systemui.statusbar.notification.people.peopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -51,7 +55,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.anyInt
@@ -67,10 +70,12 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class NotificationContentViewTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private val factory: EntryAdapterFactory = kosmos.entryAdapterFactory
 
     private lateinit var row: ExpandableNotificationRow
     private lateinit var fakeParent: ViewGroup
-    @Mock private lateinit var mPeopleNotificationIdentifier: PeopleNotificationIdentifier
 
     private val testableResources = mContext.getOrCreateTestableResources()
     private val contractedHeight =
@@ -82,24 +87,19 @@
     fun setup() {
         initMocks(this)
         fakeParent =
-            spy(FrameLayout(mContext, /* attrs= */ null).also { it.visibility = View.GONE })
-        val mockEntry = createMockNotificationEntry()
-        val mockEntryAdapter = createMockNotificationEntryAdapter()
+            spy(FrameLayout(mContext, /* attrs= */ null)).also { it.visibility = View.GONE }
+        val entry = kosmos.msgStyleBubbleableFullPerson
+        val mockEntryAdapter = factory.create(entry)
         row =
             spy(
                 when (NotificationBundleUi.isEnabled) {
                     true -> {
-                        ExpandableNotificationRow(
-                            mContext,
-                            /* attrs= */ null,
-                            UserHandle.CURRENT
-                        ).apply {
-                            entryAdapter = mockEntryAdapter
-                        }
+                        ExpandableNotificationRow(mContext, /* attrs= */ null, UserHandle.CURRENT)
+                            .apply { entryAdapter = mockEntryAdapter }
                     }
                     false -> {
-                        ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply {
-                            entryLegacy = mockEntry
+                        ExpandableNotificationRow(mContext, /* attrs= */ null, entry).apply {
+                            entryLegacy = entry
                         }
                     }
                 }
@@ -406,11 +406,11 @@
     fun setExpandedChild_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
         // Given: bottom margin of actionListMarginTarget is notificationContentMargin
         // Bubble button should not be shown for the given NotificationEntry
-        val mockNotificationEntry = createMockNotificationEntry()
+        val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson
         val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
         val actionListMarginTarget =
             spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
-        val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+        val mockExpandedChild = createMockExpandedChild()
         whenever(
                 mockExpandedChild.findViewById<LinearLayout>(
                     R.id.notification_action_list_margin_target
@@ -434,11 +434,11 @@
     fun setExpandedChild_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
         // Given: bottom margin of actionListMarginTarget is notificationContentMargin
         // Bubble button should be shown for the given NotificationEntry
-        val mockNotificationEntry = createMockNotificationEntry()
+        val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson
         val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
         val actionListMarginTarget =
             spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
-        val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+        val mockExpandedChild = createMockExpandedChild()
         whenever(
                 mockExpandedChild.findViewById<LinearLayout>(
                     R.id.notification_action_list_margin_target
@@ -463,11 +463,11 @@
     @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
     fun onNotificationUpdated_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
         // Given: bottom margin of actionListMarginTarget is notificationContentMargin
-        val mockNotificationEntry = createMockNotificationEntry()
+        val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson
         val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
         val actionListMarginTarget =
             spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
-        val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+        val mockExpandedChild = createMockExpandedChild()
         whenever(
                 mockExpandedChild.findViewById<LinearLayout>(
                     R.id.notification_action_list_margin_target
@@ -482,7 +482,7 @@
 
         // When: call NotificationContentView.onNotificationUpdated() to update the
         // NotificationEntry, which should not show bubble button
-        view.onNotificationUpdated(createMockNotificationEntry())
+        view.onNotificationUpdated(kosmos.msgStyleBubbleableFullPerson)
 
         // Then: bottom margin of actionListMarginTarget should not change, still be 20
         assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
@@ -492,11 +492,11 @@
     @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
     fun onNotificationUpdated_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
         // Given: bottom margin of actionListMarginTarget is notificationContentMargin
-        val mockNotificationEntry = createMockNotificationEntry()
+        val mockNotificationEntry = kosmos.msgStyleBubbleableFullPerson
         val mockContainingNotification = createMockContainingNotification(mockNotificationEntry)
         val actionListMarginTarget =
             spy(createLinearLayoutWithBottomMargin(notificationContentMargin))
-        val mockExpandedChild = createMockExpandedChild(mockNotificationEntry)
+        val mockExpandedChild = createMockExpandedChild()
         whenever(
                 mockExpandedChild.findViewById<LinearLayout>(
                     R.id.notification_action_list_margin_target
@@ -510,7 +510,7 @@
 
         // When: call NotificationContentView.onNotificationUpdated() to update the
         // NotificationEntry, which should show bubble button
-        view.onNotificationUpdated(createMockNotificationEntry(/*true*/ ))
+        view.onNotificationUpdated(kosmos.msgStyleBubbleableFullPerson)
 
         // Then: no bubble yet
         assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
@@ -615,15 +615,17 @@
 
     private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
         mock<ExpandableNotificationRow>().apply {
-            whenever(this.entry).thenReturn(notificationEntry)
+            if (!NotificationBundleUi.isEnabled) {
+                whenever(this.entryLegacy).thenReturn(notificationEntry)
+            }
             whenever(this.context).thenReturn(mContext)
             whenever(this.bubbleClickListener).thenReturn(View.OnClickListener {})
-            whenever(this.entryAdapter).thenReturn(createMockNotificationEntryAdapter())
+            whenever(this.entryAdapter).thenReturn(factory.create(notificationEntry))
         }
 
     private fun createMockNotificationEntry() =
         mock<NotificationEntry>().apply {
-            whenever(mPeopleNotificationIdentifier.getPeopleNotificationType(this))
+            whenever(kosmos.peopleNotificationIdentifier.getPeopleNotificationType(this))
                 .thenReturn(PeopleNotificationIdentifier.TYPE_FULL_PERSON)
             whenever(this.bubbleMetadata).thenReturn(mock())
             val sbnMock: StatusBarNotification = mock()
@@ -632,7 +634,8 @@
             whenever(sbnMock.user).thenReturn(userMock)
         }
 
-    private fun createMockNotificationEntryAdapter() = mock<EntryAdapter>()
+    private fun createMockNotificationEntryAdapter() =
+        mock<EntryAdapter>()
 
     private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
         val outerLayout = LinearLayout(mContext)
@@ -643,7 +646,7 @@
         return innerLayout
     }
 
-    private fun createMockExpandedChild(notificationEntry: NotificationEntry) =
+    private fun createMockExpandedChild() =
         spy(createViewWithHeight(expandedHeight)).apply {
             whenever(this.findViewById<ImageView>(R.id.bubble_button)).thenReturn(mock())
             whenever(this.findViewById<View>(R.id.actions_container)).thenReturn(mock())
@@ -664,9 +667,16 @@
         val height = if (isSystemExpanded) expandedHeight else contractedHeight
         doReturn(height).whenever(row).intrinsicHeight
 
-        return spy(NotificationContentView(mContext, /* attrs= */ null))
+        return NotificationContentView(mContext, /* attrs= */ null)
             .apply {
-                initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock(), mock())
+                initialize(
+                    kosmos.peopleNotificationIdentifier,
+                    mock(),
+                    mock(),
+                    mock(),
+                    mock(),
+                    mock(),
+                )
                 setContainingNotification(row)
                 setHeights(
                     /* smallHeight= */ contractedHeight,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 8fb2a24..6a9a485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -734,6 +734,7 @@
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
         NotificationEntry entry = mock(NotificationEntry.class);
         when(row.getEntry()).thenReturn(entry);
+        when(row.getEntryLegacy()).thenReturn(entry);
         when(entry.isAmbient()).thenReturn(false);
         EntryAdapter entryAdapter = mock(EntryAdapter.class);
         when(entryAdapter.isAmbient()).thenReturn(false);
@@ -753,6 +754,7 @@
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
         NotificationEntry entry = mock(NotificationEntry.class);
         when(row.getEntry()).thenReturn(entry);
+        when(row.getEntryLegacy()).thenReturn(entry);
         when(entry.isAmbient()).thenReturn(true);
         EntryAdapter entryAdapter = mock(EntryAdapter.class);
         when(entryAdapter.isAmbient()).thenReturn(true);
@@ -772,6 +774,7 @@
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
         NotificationEntry entry = mock(NotificationEntry.class);
         when(row.getEntry()).thenReturn(entry);
+        when(row.getEntryLegacy()).thenReturn(entry);
         when(entry.isAmbient()).thenReturn(false);
         EntryAdapter entryAdapter = mock(EntryAdapter.class);
         when(entryAdapter.isAmbient()).thenReturn(false);
@@ -1384,6 +1387,7 @@
         NotificationEntry entry = mock(NotificationEntry.class);
         when(entry.isSeenInShade()).thenReturn(true);
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+        when(row.getEntryLegacy()).thenReturn(entry);
         when(row.getEntry()).thenReturn(entry);
 
         // WHEN we generate an add event
@@ -1440,6 +1444,7 @@
         NotificationEntry entry = mock(NotificationEntry.class);
         when(row.canViewBeCleared()).thenReturn(true);
         when(row.getEntry()).thenReturn(entry);
+        when(row.getEntryLegacy()).thenReturn(entry);
         when(entry.isClearable()).thenReturn(true);
         EntryAdapter entryAdapter = mock(EntryAdapter.class);
         when(entryAdapter.isClearable()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index a3616d2..a7f3fdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -28,8 +28,8 @@
 import static android.provider.Settings.Global.HEADS_UP_ON;
 
 import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE;
-import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION;
 import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
+import static com.android.systemui.shared.Flags.FLAG_AMBIENT_AOD;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.phone.CentralSurfaces.MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU;
@@ -242,7 +242,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @RunWithLooper(setAsMainLooper = true)
-@EnableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
+@EnableFlags(FLAG_AMBIENT_AOD)
 public class CentralSurfacesImplTest extends SysuiTestCase {
 
     private static final DeviceState FOLD_STATE_FOLDED = new DeviceState(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 15cb95a..846db63 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -15,6 +15,7 @@
  */
 package com.android.systemui;
 
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -49,11 +50,13 @@
 
 import org.junit.After;
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.mockito.Mockito;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -190,6 +193,7 @@
 
     @Before
     public void SysuiSetup() throws Exception {
+        assertTempFilesAreCreatable();
         ProtoLog.REQUIRE_PROTOLOGTOOL = false;
         mSysuiDependency = new SysuiTestDependency(mContext, shouldFailOnLeakedReceiver());
         mDependency = mSysuiDependency.install();
@@ -211,6 +215,28 @@
         }
     }
 
+    private static Boolean sCanCreateTempFiles = null;
+
+    private static void assertTempFilesAreCreatable() {
+        // TODO(b/391948934): hopefully remove this hack
+        if (sCanCreateTempFiles == null) {
+            try {
+                File tempFile = File.createTempFile("confirm_temp_file_createable", "txt");
+                sCanCreateTempFiles = true;
+                assertTrue(tempFile.delete());
+            } catch (IOException e) {
+                sCanCreateTempFiles = false;
+                throw new RuntimeException(e);
+            }
+        }
+        if (!sCanCreateTempFiles) {
+            Assert.fail(
+                    "Cannot create temp files, so mockito will probably fail (b/391948934).  Temp"
+                            + " folder should be: "
+                            + System.getProperty("java.io.tmpdir"));
+        }
+    }
+
     protected boolean shouldFailOnLeakedReceiver() {
         return false;
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 338e4be..122e6a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.display.data.repository
 
 import android.view.Display
+import com.android.app.displaylib.DisplayRepository.PendingDisplay
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.mockito.mock
 import dagger.Binds
@@ -26,7 +27,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import com.android.app.displaylib.DisplayRepository.PendingDisplay
 import org.mockito.Mockito.`when` as whenever
 
 /** Creates a mock display. */
@@ -50,8 +50,7 @@
 class FakeDisplayRepository @Inject constructor() : DisplayRepository {
     private val flow = MutableStateFlow<Set<Display>>(emptySet())
     private val displayIdFlow = MutableStateFlow<Set<Int>>(emptySet())
-    private val pendingDisplayFlow =
-        MutableSharedFlow<PendingDisplay?>(replay = 1)
+    private val pendingDisplayFlow = MutableSharedFlow<PendingDisplay?>(replay = 1)
     private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0)
     private val displayRemovalEventFlow = MutableSharedFlow<Int>(replay = 0)
     private val displayIdsWithSystemDecorationsFlow = MutableStateFlow<Set<Int>>(emptySet())
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
index 5ab3b3d..161e062 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
@@ -16,6 +16,10 @@
 
 package com.android.systemui.display.data.repository
 
+import com.android.app.displaylib.DisplayInstanceLifecycleManager
+import com.android.app.displaylib.FakeDisplayInstanceLifecycleManager
+import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown
+import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl
 import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -67,13 +71,25 @@
     Kosmos.Fixture { FakePerDisplayInstanceProviderWithTeardown() }
 
 val Kosmos.perDisplayDumpHelper by Kosmos.Fixture { PerDisplayRepoDumpHelper(dumpManager) }
+val Kosmos.fakeDisplayInstanceLifecycleManager by
+    Kosmos.Fixture { FakeDisplayInstanceLifecycleManager() }
+
 val Kosmos.fakePerDisplayInstanceRepository by
     Kosmos.Fixture {
-        PerDisplayInstanceRepositoryImpl(
-            debugName = "fakePerDisplayInstanceRepository",
-            instanceProvider = fakePerDisplayInstanceProviderWithTeardown,
-            testScope.backgroundScope,
-            displayRepository,
-            perDisplayDumpHelper,
-        )
+        { lifecycleManager: DisplayInstanceLifecycleManager? ->
+            PerDisplayInstanceRepositoryImpl(
+                debugName = "fakePerDisplayInstanceRepository",
+                instanceProvider = fakePerDisplayInstanceProviderWithTeardown,
+                lifecycleManager,
+                testScope.backgroundScope,
+                displayRepository,
+                perDisplayDumpHelper,
+            )
+        }
     }
+
+fun Kosmos.createPerDisplayInstanceRepository(
+    overrideLifecycleManager: DisplayInstanceLifecycleManager? = null
+): PerDisplayInstanceRepositoryImpl<TestPerDisplayInstance> {
+    return fakePerDisplayInstanceRepository(overrideLifecycleManager)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 623989e..c80d738 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -81,6 +81,7 @@
 import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.statusbar.notification.row.entryAdapterFactory
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.statusbar.phone.fakeAutoHideControllerStore
@@ -206,4 +207,5 @@
     val displayTracker by lazy { kosmos.displayTracker }
     val fakeShadeDisplaysRepository by lazy { kosmos.fakeShadeDisplaysRepository }
     val sysUIStateDispatcher by lazy { kosmos.sysUIStateDispatcher }
+    val entryAdapterFactory by lazy { kosmos.entryAdapterFactory }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt
index dc22905..56fd270 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt
@@ -18,5 +18,11 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
 
-val Kosmos.detailsViewModel by Kosmos.Fixture { DetailsViewModel(currentTilesInteractor) }
+val Kosmos.detailsViewModel by Kosmos.Fixture {
+    DetailsViewModel(
+        currentTilesInteractor,
+        shadeModeInteractor
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt
deleted file mode 100644
index 59f5ecd..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationEntryBuilderKosmos.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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
-
-import android.app.Notification
-import android.app.PendingIntent
-import android.app.Person
-import android.content.Intent
-import android.content.applicationContext
-import android.graphics.drawable.Icon
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.icon.IconPack
-import com.android.systemui.statusbar.notification.promoted.setPromotedContent
-import org.mockito.kotlin.mock
-
-fun Kosmos.setIconPackWithMockIconViews(entry: NotificationEntry) {
-    entry.icons =
-        IconPack.buildPack(
-            /* statusBarIcon = */ mock(),
-            /* statusBarChipIcon = */ mock(),
-            /* shelfIcon = */ mock(),
-            /* aodIcon = */ mock(),
-            /* source = */ null,
-        )
-}
-
-fun Kosmos.buildOngoingCallEntry(
-    promoted: Boolean = false,
-    block: NotificationEntryBuilder.() -> Unit = {},
-): NotificationEntry =
-    buildNotificationEntry(
-        tag = "call",
-        promoted = promoted,
-        style = makeOngoingCallStyle(),
-        block = block,
-    )
-
-fun Kosmos.buildPromotedOngoingEntry(
-    block: NotificationEntryBuilder.() -> Unit = {}
-): NotificationEntry =
-    buildNotificationEntry(tag = "ron", promoted = true, style = null, block = block)
-
-fun Kosmos.buildNotificationEntry(
-    tag: String? = null,
-    promoted: Boolean = false,
-    style: Notification.Style? = null,
-    block: NotificationEntryBuilder.() -> Unit = {},
-): NotificationEntry =
-    NotificationEntryBuilder()
-        .apply {
-            setTag(tag)
-            setFlag(applicationContext, Notification.FLAG_PROMOTED_ONGOING, promoted)
-            modifyNotification(applicationContext)
-                .setSmallIcon(Icon.createWithContentUri("content://null"))
-                .setStyle(style)
-        }
-        .apply(block)
-        .build()
-        .also {
-            setIconPackWithMockIconViews(it)
-            if (promoted) setPromotedContent(it)
-        }
-
-private fun Kosmos.makeOngoingCallStyle(): Notification.CallStyle {
-    val pendingIntent =
-        PendingIntent.getBroadcast(
-            applicationContext,
-            0,
-            Intent("action"),
-            PendingIntent.FLAG_IMMUTABLE,
-        )
-    val person = Person.Builder().setName("person").build()
-    return Notification.CallStyle.forOngoingCall(person, pendingIntent)
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt
new file mode 100644
index 0000000..00c6c94
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilderKosmos.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.collection
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.PendingIntent
+import android.app.Person
+import android.content.Intent
+import android.content.applicationContext
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import com.android.systemui.activity.EmptyTestActivity
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.icon.IconPack
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
+import com.android.systemui.statusbar.notification.promoted.setPromotedContent
+import org.mockito.kotlin.mock
+
+fun Kosmos.setIconPackWithMockIconViews(entry: NotificationEntry) {
+    entry.icons =
+        IconPack.buildPack(
+            /* statusBarIcon = */ mock(),
+            /* statusBarChipIcon = */ mock(),
+            /* shelfIcon = */ mock(),
+            /* aodIcon = */ mock(),
+            /* source = */ null,
+        )
+}
+
+fun Kosmos.buildPromotedOngoingEntry(
+    block: NotificationEntryBuilder.() -> Unit = {}
+): NotificationEntry =
+    buildNotificationEntry(tag = "ron", promoted = true, style = null, block = block)
+
+fun Kosmos.buildOngoingCallEntry(
+    promoted: Boolean = false,
+    block: NotificationEntryBuilder.() -> Unit = {},
+): NotificationEntry =
+    buildNotificationEntry(
+        tag = "call",
+        promoted = promoted,
+        style = makeOngoingCallStyle(),
+        block = block,
+    )
+
+fun Kosmos.buildNotificationEntry(
+    tag: String? = null,
+    promoted: Boolean = false,
+    style: Notification.Style? = null,
+    block: NotificationEntryBuilder.() -> Unit = {},
+): NotificationEntry =
+    NotificationEntryBuilder()
+        .apply {
+            setTag(tag)
+            setFlag(applicationContext, Notification.FLAG_PROMOTED_ONGOING, promoted)
+            modifyNotification(applicationContext)
+                .setSmallIcon(Icon.createWithContentUri("content://null"))
+                .setStyle(style)
+        }
+        .apply(block)
+        .build()
+        .also {
+            setIconPackWithMockIconViews(it)
+            if (promoted) setPromotedContent(it)
+        }
+
+private fun Kosmos.makeOngoingCallStyle(): Notification.CallStyle {
+    val pendingIntent =
+        PendingIntent.getBroadcast(
+            applicationContext,
+            0,
+            Intent("action"),
+            PendingIntent.FLAG_IMMUTABLE,
+        )
+    val person = Person.Builder().setName("person").build()
+    return Notification.CallStyle.forOngoingCall(person, pendingIntent)
+}
+
+private fun Kosmos.makeMessagingStyleNotification(): Notification.Builder {
+    val personIcon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+    val person = Person.Builder().setIcon(personIcon).setName("Person").build()
+    val message = Notification.MessagingStyle.Message("Message!", 4323, person)
+    val bubbleIntent =
+        PendingIntent.getActivity(
+            applicationContext,
+            0,
+            Intent(applicationContext, EmptyTestActivity::class.java),
+            PendingIntent.FLAG_MUTABLE,
+        )
+
+    return Notification.Builder(applicationContext, "channelId")
+        .setSmallIcon(R.drawable.ic_person)
+        .setContentTitle("Title")
+        .setContentText("Text")
+        .setStyle(Notification.MessagingStyle(person).addMessage(message))
+        .setBubbleMetadata(
+            Notification.BubbleMetadata.Builder(
+                    bubbleIntent,
+                    Icon.createWithResource(applicationContext, R.drawable.android),
+                )
+                .setDeleteIntent(mock<PendingIntent>())
+                .setDesiredHeight(314)
+                .setAutoExpandBubble(false)
+                .build()
+        )
+}
+
+fun Kosmos.makeEntryOfPeopleType(@PeopleNotificationType type: Int): NotificationEntryBuilder {
+    val channel = NotificationChannel("messages", "messages", IMPORTANCE_DEFAULT)
+    channel.isImportantConversation = (type == TYPE_IMPORTANT_PERSON)
+    channel.setConversationId("parent", "convo")
+
+    val entry =
+        NotificationEntryBuilder().apply {
+            updateRanking {
+                it.setIsConversation(type != TYPE_NON_PERSON)
+                it.setShortcutInfo(if (type >= TYPE_FULL_PERSON) mock() else null)
+                it.setChannel(channel)
+            }
+            setNotification(makeMessagingStyleNotification().build())
+        }
+    return entry
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.kt
new file mode 100644
index 0000000..e127a70
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryKosmos.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.statusbar.notification.collection
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
+
+val Kosmos.msgStyleBubbleableFullPerson by
+    Kosmos.Fixture { makeEntryOfPeopleType(TYPE_FULL_PERSON).build() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt
index 09f9f1c..44d7a22 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProviderKosmos.kt
@@ -18,6 +18,7 @@
 
 import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
 
@@ -25,6 +26,7 @@
     Kosmos.Fixture {
         VolumeDialogSliderIconProvider(
             context = applicationContext,
+            uiBackgroundContext = backgroundCoroutineContext,
             audioVolumeInteractor = audioVolumeInteractor,
             zenModeInteractor = zenModeInteractor,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt
index 8c8d024..6e43d79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt
@@ -16,9 +16,11 @@
 
 package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
 
+import android.content.applicationContext
 import com.android.internal.logging.uiEventLogger
 import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
 import com.android.systemui.volume.domain.interactor.audioSharingInteractor
 import com.android.systemui.volume.shared.volumePanelLogger
 import kotlinx.coroutines.CoroutineScope
@@ -28,7 +30,9 @@
         object : AudioSharingStreamSliderViewModel.Factory {
             override fun create(coroutineScope: CoroutineScope): AudioSharingStreamSliderViewModel {
                 return AudioSharingStreamSliderViewModel(
+                    applicationContext,
                     coroutineScope,
+                    backgroundCoroutineContext,
                     audioSharingInteractor,
                     uiEventLogger,
                     sliderHapticsViewModelFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
index 88c716e..47016e5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.internal.logging.uiEventLogger
 import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.volume.domain.interactor.audioSharingInteractor
 import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
@@ -37,6 +38,7 @@
                 return AudioStreamSliderViewModel(
                     audioStream,
                     coroutineScope,
+                    backgroundCoroutineContext,
                     applicationContext,
                     audioVolumeInteractor,
                     zenModeInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
index 6875619..ed51e05 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt
@@ -19,6 +19,7 @@
 import android.content.applicationContext
 import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
 import com.android.systemui.volume.mediaDeviceSessionInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
 import com.android.systemui.volume.shared.volumePanelLogger
@@ -34,6 +35,7 @@
                 return CastVolumeSliderViewModel(
                     session,
                     coroutineScope,
+                    backgroundCoroutineContext,
                     applicationContext,
                     mediaDeviceSessionInteractor,
                     sliderHapticsViewModelFactory,
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
index b956e44..90a9271 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
@@ -281,7 +281,6 @@
                             },
                     )
                 }
-                downstreamSet.clear()
             }
         }
         reset()
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
index c11eb12..81f3702 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
@@ -145,7 +145,14 @@
                 val conn = branchNode.upstream
                 severed.add(conn)
                 conn.removeDownstream(downstream = branchNode.schedulable)
-                depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                if (conn.depthTracker.snapshotIsDirect) {
+                    depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                } else {
+                    depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth)
+                    depthTracker.updateIndirectRoots(
+                        removals = conn.depthTracker.snapshotIndirectRoots
+                    )
+                }
             }
         }
 
@@ -156,7 +163,14 @@
                 val conn = branchNode.upstream
                 severed.add(conn)
                 conn.removeDownstream(downstream = branchNode.schedulable)
-                depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                if (conn.depthTracker.snapshotIsDirect) {
+                    depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                } else {
+                    depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth)
+                    depthTracker.updateIndirectRoots(
+                        removals = conn.depthTracker.snapshotIndirectRoots
+                    )
+                }
             }
 
             // add new
@@ -343,13 +357,8 @@
                 val (patchesConn, needsEval) =
                     getPatches(evalScope).activate(evalScope, downstream = muxNode.schedulable)
                         ?: run {
-                            // Turns out we can't connect to patches, so update our depth and
-                            // propagate
-                            if (muxNode.depthTracker.setIsIndirectRoot(false)) {
-                                // TODO: schedules might not be necessary now that we're not
-                                // parallel?
-                                muxNode.depthTracker.schedule(evalScope.scheduler, muxNode)
-                            }
+                            // Turns out we can't connect to patches, so update our depth
+                            muxNode.depthTracker.setIsIndirectRoot(false)
                             return
                         }
                 muxNode.patches = patchesConn
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
index cb2c6e5..faef6a2 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
@@ -109,7 +109,14 @@
                 val conn: NodeConnection<V> = branchNode.upstream
                 severed.add(conn)
                 conn.removeDownstream(downstream = branchNode.schedulable)
-                depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                if (conn.depthTracker.snapshotIsDirect) {
+                    depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                } else {
+                    depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth)
+                    depthTracker.updateIndirectRoots(
+                        removals = conn.depthTracker.snapshotIndirectRoots
+                    )
+                }
             }
         }
 
@@ -123,7 +130,14 @@
                 val conn: NodeConnection<V> = oldBranch.upstream
                 severed.add(conn)
                 conn.removeDownstream(downstream = oldBranch.schedulable)
-                depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                if (conn.depthTracker.snapshotIsDirect) {
+                    depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                } else {
+                    depthTracker.removeIndirectUpstream(conn.depthTracker.snapshotIndirectDepth)
+                    depthTracker.updateIndirectRoots(
+                        removals = conn.depthTracker.snapshotIndirectRoots
+                    )
+                }
             }
 
             // add new
diff --git a/packages/WallpaperBackup/Android.bp b/packages/WallpaperBackup/Android.bp
index b8e0d42..b0c0c3a 100644
--- a/packages/WallpaperBackup/Android.bp
+++ b/packages/WallpaperBackup/Android.bp
@@ -48,7 +48,9 @@
     static_libs: [
         "androidx.test.core",
         "androidx.test.rules",
+        "flag-junit",
         "mockito-target-minus-junit4",
+        "platform-test-annotations",
         "truth",
     ],
     resource_dirs: ["test/res"],
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 44ea9a2..a80a64d 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -16,6 +16,7 @@
 
 package com.android.wallpaperbackup;
 
+import static android.app.Flags.liveWallpaperContentHandling;
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
 import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
@@ -27,6 +28,7 @@
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED;
 import static com.android.window.flags.Flags.multiCrop;
 
+import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.app.WallpaperManager;
 import android.app.backup.BackupAgent;
@@ -35,6 +37,7 @@
 import android.app.backup.BackupManager;
 import android.app.backup.BackupRestoreEventLogger.BackupRestoreError;
 import android.app.backup.FullBackupDataOutput;
+import android.app.wallpaper.WallpaperDescription;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -493,11 +496,13 @@
 
             // First parse the live component name so that we know for logging if we care about
             // logging errors with the image restore.
-            ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
-            mSystemHasLiveComponent = wpService != null;
+            Pair<ComponentName, WallpaperDescription> wpService = parseWallpaperComponent(infoStage,
+                    "wp");
+            mSystemHasLiveComponent = wpService.first != null;
 
-            ComponentName kwpService = parseWallpaperComponent(infoStage, "kwp");
-            mLockHasLiveComponent = kwpService != null;
+            Pair<ComponentName, WallpaperDescription> kwpService = parseWallpaperComponent(
+                    infoStage, "kwp");
+            mLockHasLiveComponent = kwpService.first != null;
             boolean separateLockWallpaper = mLockHasLiveComponent || lockImageStage.exists();
 
             // if there's no separate lock wallpaper, apply the system wallpaper to both screens.
@@ -586,25 +591,39 @@
     }
 
     @VisibleForTesting
-    void updateWallpaperComponent(ComponentName wpService, int which)
+    void updateWallpaperComponent(Pair<ComponentName, WallpaperDescription> wpService, int which)
             throws IOException {
-        if (servicePackageExists(wpService)) {
-            Slog.i(TAG, "Using wallpaper service " + wpService);
-            mWallpaperManager.setWallpaperComponentWithFlags(wpService, which);
-            if ((which & FLAG_LOCK) != 0) {
-                mEventLogger.onLockLiveWallpaperRestored(wpService);
-            }
-            if ((which & FLAG_SYSTEM) != 0) {
-                mEventLogger.onSystemLiveWallpaperRestored(wpService);
+        WallpaperDescription description = wpService.second;
+        boolean hasDescription = (liveWallpaperContentHandling() && description != null);
+        ComponentName component = hasDescription ? description.getComponent() : wpService.first;
+        if (servicePackageExists(component)) {
+            if (hasDescription) {
+                Slog.i(TAG, "Using wallpaper description " + description);
+                mWallpaperManager.setWallpaperComponentWithDescription(description, which);
+                if ((which & FLAG_LOCK) != 0) {
+                    mEventLogger.onLockLiveWallpaperRestoredWithDescription(description);
+                }
+                if ((which & FLAG_SYSTEM) != 0) {
+                    mEventLogger.onSystemLiveWallpaperRestoredWithDescription(description);
+                }
+            } else {
+                Slog.i(TAG, "Using wallpaper service " + component);
+                mWallpaperManager.setWallpaperComponentWithFlags(component, which);
+                if ((which & FLAG_LOCK) != 0) {
+                    mEventLogger.onLockLiveWallpaperRestored(component);
+                }
+                if ((which & FLAG_SYSTEM) != 0) {
+                    mEventLogger.onSystemLiveWallpaperRestored(component);
+                }
             }
         } else {
             // If we've restored a live wallpaper, but the component doesn't exist,
             // we should log it as an error so we can easily identify the problem
             // in reports from users
-            if (wpService != null) {
+            if (component != null) {
                 // TODO(b/268471749): Handle delayed case
-                applyComponentAtInstall(wpService, which);
-                Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. "
+                applyComponentAtInstall(component, description, which);
+                Slog.w(TAG, "Wallpaper service " + component + " isn't available. "
                         + " Will try to apply later");
             }
         }
@@ -697,7 +716,6 @@
      * (thereby preserving the center point). Then finally, adding any leftover image real-estate
      * (i.e. space left over on the horizontal axis) to add parallax effect. Parallax is only added
      * if was present in the old device's settings.
-     *
      */
     private Rect findNewCropfromOldCrop(Rect oldCrop, Point oldDisplaySize, boolean oldRtl,
             Point newDisplaySize, Point bitmapSize, boolean newRtl) {
@@ -976,10 +994,12 @@
         return cropHints;
     }
 
-    private ComponentName parseWallpaperComponent(File wallpaperInfo, String sectionTag) {
+    private Pair<ComponentName, WallpaperDescription> parseWallpaperComponent(File wallpaperInfo,
+            String sectionTag) {
         ComponentName name = null;
+        WallpaperDescription description = null;
         try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
-            final XmlPullParser parser = Xml.resolvePullParser(stream);
+            final TypedXmlPullParser parser = Xml.resolvePullParser(stream);
 
             int type;
             do {
@@ -991,6 +1011,7 @@
                         name = (parsedName != null)
                                 ? ComponentName.unflattenFromString(parsedName)
                                 : null;
+                        description = parseWallpaperDescription(parser, name);
                         break;
                     }
                 }
@@ -998,9 +1019,30 @@
         } catch (Exception e) {
             // Whoops; can't process the info file at all.  Report failure.
             Slog.w(TAG, "Failed to parse restored component: " + e.getMessage());
-            return null;
+            return new Pair<>(null, null);
         }
-        return name;
+        return new Pair<>(name, description);
+    }
+
+    // Copied from com.android.server.wallpaper.WallpaperDataParser
+    private WallpaperDescription parseWallpaperDescription(TypedXmlPullParser parser,
+            ComponentName component) throws XmlPullParserException, IOException {
+
+        WallpaperDescription description = null;
+        int type = parser.next();
+        if (type == XmlPullParser.START_TAG && "description".equals(parser.getName())) {
+            // Always read the description if it's there - there may be one from a previous save
+            // with content handling enabled even if it's enabled now
+            description = WallpaperDescription.restoreFromXml(parser);
+            if (liveWallpaperContentHandling()) {
+                // null component means that wallpaper was last saved without content handling, so
+                // populate description from saved component
+                if (description.getComponent() == null) {
+                    description = description.toBuilder().setComponent(component).build();
+                }
+            }
+        }
+        return description;
     }
 
     private int getAttributeInt(XmlPullParser parser, String name, int defValue) {
@@ -1037,14 +1079,16 @@
         // Intentionally blank
     }
 
-    private void applyComponentAtInstall(ComponentName componentName, int which) {
-        PackageMonitor packageMonitor = getWallpaperPackageMonitor(
-                componentName, which);
+    private void applyComponentAtInstall(ComponentName componentName,
+            @Nullable WallpaperDescription description, int which) {
+        PackageMonitor packageMonitor = getWallpaperPackageMonitor(componentName, description,
+                which);
         packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true);
     }
 
     @VisibleForTesting
-    PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) {
+    PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
+            @Nullable WallpaperDescription description, int which) {
         return new PackageMonitor() {
             @Override
             public void onPackageAdded(String packageName, int uid) {
@@ -1068,7 +1112,33 @@
                     return;
                 }
 
-                if (componentName.getPackageName().equals(packageName)) {
+                boolean useDescription = (liveWallpaperContentHandling() && description != null
+                        && description.getComponent() != null);
+                if (useDescription && description.getComponent().getPackageName().equals(
+                        packageName)) {
+                    Slog.d(TAG, "Applying description " + description);
+                    boolean success = mWallpaperManager.setWallpaperComponentWithDescription(
+                            description, which);
+                    WallpaperEventLogger logger = new WallpaperEventLogger(
+                            mBackupManager.getDelayedRestoreLogger());
+                    if (success) {
+                        if ((which & FLAG_SYSTEM) != 0) {
+                            logger.onSystemLiveWallpaperRestoredWithDescription(description);
+                        }
+                        if ((which & FLAG_LOCK) != 0) {
+                            logger.onLockLiveWallpaperRestoredWithDescription(description);
+                        }
+                    } else {
+                        if ((which & FLAG_SYSTEM) != 0) {
+                            logger.onSystemLiveWallpaperRestoreFailed(
+                                    WallpaperEventLogger.ERROR_SET_DESCRIPTION_EXCEPTION);
+                        }
+                        if ((which & FLAG_LOCK) != 0) {
+                            logger.onLockLiveWallpaperRestoreFailed(
+                                    WallpaperEventLogger.ERROR_SET_DESCRIPTION_EXCEPTION);
+                        }
+                    }
+                } else if (componentName.getPackageName().equals(packageName)) {
                     Slog.d(TAG, "Applying component " + componentName);
                     boolean success = mWallpaperManager.setWallpaperComponentWithFlags(
                             componentName, which);
@@ -1191,4 +1261,4 @@
     void setBackupManagerForTesting(BackupManager backupManager) {
         mBackupManager = backupManager;
     }
-}
\ No newline at end of file
+}
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java
index b25f95a..69469e4 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java
@@ -16,12 +16,14 @@
 
 package com.android.wallpaperbackup;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.WallpaperInfo;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
 import android.app.backup.BackupRestoreEventLogger.BackupRestoreError;
+import android.app.wallpaper.WallpaperDescription;
 import android.content.ComponentName;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -53,6 +55,16 @@
     @VisibleForTesting
     static final String WALLPAPER_LIVE_LOCK = "wlp_live_lock";
 
+    /* Live component used as system (or home) screen wallpaper */
+    @BackupRestoreDataType
+    @VisibleForTesting
+    static final String WALLPAPER_DESCRIPTION_SYSTEM = "wlp_description_system";
+
+    /* Live component used as lock screen wallpaper */
+    @BackupRestoreDataType
+    @VisibleForTesting
+    static final String WALLPAPER_DESCRIPTION_LOCK = "wlp_description_lock";
+
     @BackupRestoreError
     static final String ERROR_INELIGIBLE = "ineligible";
     @BackupRestoreError
@@ -64,6 +76,8 @@
     @BackupRestoreError
     static final String ERROR_SET_COMPONENT_EXCEPTION = "exception_in_set_component";
     @BackupRestoreError
+    static final String ERROR_SET_DESCRIPTION_EXCEPTION = "exception_in_set_description";
+    @BackupRestoreError
     static final String ERROR_LIVE_PACKAGE_NOT_INSTALLED = "live_pkg_not_installed_in_restore";
 
     private final BackupRestoreEventLogger mLogger;
@@ -115,11 +129,11 @@
     }
 
     void onSystemImageWallpaperRestored() {
-        logRestoreSuccessInternal(WALLPAPER_IMG_SYSTEM, /* liveComponentWallpaperInfo */ null);
+        logRestoreSuccessInternal(WALLPAPER_IMG_SYSTEM, (ComponentName) null);
     }
 
     void onLockImageWallpaperRestored() {
-        logRestoreSuccessInternal(WALLPAPER_IMG_LOCK, /* liveComponentWallpaperInfo */ null);
+        logRestoreSuccessInternal(WALLPAPER_IMG_LOCK, (ComponentName) null);
     }
 
     void onSystemLiveWallpaperRestored(ComponentName wpService) {
@@ -146,6 +160,13 @@
         logRestoreFailureInternal(WALLPAPER_LIVE_LOCK, error);
     }
 
+    void onSystemLiveWallpaperRestoredWithDescription(@NonNull WallpaperDescription description) {
+        logRestoreSuccessInternal(WALLPAPER_DESCRIPTION_SYSTEM, description);
+    }
+
+    void onLockLiveWallpaperRestoredWithDescription(@NonNull WallpaperDescription description) {
+        logRestoreSuccessInternal(WALLPAPER_DESCRIPTION_LOCK, description);
+    }
 
 
     /**
@@ -168,15 +189,17 @@
      */
     void onRestoreException(Exception exception) {
         String error = exception.getClass().getName();
-        if (!mProcessedDataTypes.contains(WALLPAPER_IMG_SYSTEM) && !mProcessedDataTypes.contains(
-                WALLPAPER_LIVE_SYSTEM)) {
+        if (!(mProcessedDataTypes.contains(WALLPAPER_IMG_SYSTEM) || mProcessedDataTypes.contains(
+                WALLPAPER_LIVE_SYSTEM) || mProcessedDataTypes.contains(
+                WALLPAPER_DESCRIPTION_SYSTEM))) {
             mLogger.logItemsRestoreFailed(WALLPAPER_IMG_SYSTEM, /* count */ 1, error);
         }
-        if (!mProcessedDataTypes.contains(WALLPAPER_IMG_LOCK) && !mProcessedDataTypes.contains(
-                WALLPAPER_LIVE_LOCK)) {
+        if (!(mProcessedDataTypes.contains(WALLPAPER_IMG_LOCK) || mProcessedDataTypes.contains(
+                WALLPAPER_LIVE_LOCK) || mProcessedDataTypes.contains(WALLPAPER_DESCRIPTION_LOCK))) {
             mLogger.logItemsRestoreFailed(WALLPAPER_IMG_LOCK, /* count */ 1, error);
         }
     }
+
     private void logBackupSuccessInternal(@BackupRestoreDataType String which,
             @Nullable WallpaperInfo liveComponentWallpaperInfo) {
         mLogger.logItemsBackedUp(which, /* count */ 1);
@@ -197,6 +220,13 @@
         mProcessedDataTypes.add(which);
     }
 
+    private void logRestoreSuccessInternal(@BackupRestoreDataType String which,
+            @NonNull WallpaperDescription description) {
+        mLogger.logItemsRestored(which, /* count */ 1);
+        logRestoredLiveWallpaperDescription(which, description);
+        mProcessedDataTypes.add(which);
+    }
+
     private void logRestoreFailureInternal(@BackupRestoreDataType String which,
             @BackupRestoreError String error) {
         mLogger.logItemsRestoreFailed(which, /* count */ 1, error);
@@ -216,4 +246,11 @@
             mLogger.logRestoreMetadata(wallpaperType, wpService.getClassName());
         }
     }
+
+    private void logRestoredLiveWallpaperDescription(@BackupRestoreDataType String wallpaperType,
+            WallpaperDescription description) {
+        if (description != null) {
+            mLogger.logRestoreMetadata(wallpaperType, description.toString());
+        }
+    }
 }
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index f5fb644..c9f1b58 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -16,6 +16,7 @@
 
 package com.android.wallpaperbackup;
 
+import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
@@ -27,6 +28,8 @@
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA;
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER;
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_DESCRIPTION_LOCK;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_DESCRIPTION_SYSTEM;
 import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_LOCK;
 import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYSTEM;
 import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK;
@@ -54,6 +57,7 @@
 import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.FullBackupDataOutput;
+import android.app.wallpaper.WallpaperDescription;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -63,7 +67,10 @@
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.wallpaper.WallpaperService;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.util.Xml;
 
@@ -79,6 +86,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatcher;
@@ -113,12 +121,15 @@
     @Mock
     private BackupManager mBackupManager;
 
-    @Rule
-    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
-
     private ContextWithServiceOverrides mContext;
     private IsolatedWallpaperBackupAgent mWallpaperBackupAgent;
     private ComponentName mWallpaperComponent;
+    private WallpaperDescription mWallpaperDescription;
+
+    private final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    @Rule
+    public RuleChain mRuleChain = RuleChain.outerRule(new SetFlagsRule()).around(mTemporaryFolder);
 
     @Before
     public void setUp() {
@@ -135,6 +146,8 @@
                 BackupAnnotations.OperationType.BACKUP);
 
         mWallpaperComponent = new ComponentName(TEST_WALLPAPER_PACKAGE, "");
+        mWallpaperDescription = new WallpaperDescription.Builder().setComponent(
+                mWallpaperComponent).setId("id").build();
     }
 
     @After
@@ -366,11 +379,128 @@
     }
 
     @Test
-    public void testUpdateWallpaperComponent_systemAndLock() throws IOException {
-        mWallpaperBackupAgent.mIsDeviceInRestore = true;
-        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+    public void testUpdateWallpaperComponent_immediate_systemAndLock() throws IOException {
+        mWallpaperBackupAgent.mPackageExists = true;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
                 /* which */ FLAG_LOCK | FLAG_SYSTEM);
 
+        assertThat(mWallpaperBackupAgent.mGetPackageMonitorCallCount).isEqualTo(0);
+        verify(mWallpaperManager, times(1))
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+        verify(mWallpaperManager, never()).clear(anyInt());
+    }
+
+    @Test
+    public void testUpdateWallpaperComponent_immediate_systemOnly()
+            throws IOException {
+        mWallpaperBackupAgent.mPackageExists = true;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
+                /* which */ FLAG_SYSTEM);
+
+        assertThat(mWallpaperBackupAgent.mGetPackageMonitorCallCount).isEqualTo(0);
+        verify(mWallpaperManager, times(1))
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+        verify(mWallpaperManager, never()).clear(anyInt());
+    }
+
+    @Test
+    @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    public void testUpdateWallpaperDescription_immediate_systemAndLock()
+            throws IOException {
+        mWallpaperBackupAgent.mPackageExists = true;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(
+                new Pair<>(mWallpaperComponent, mWallpaperDescription), /* which */
+                FLAG_LOCK | FLAG_SYSTEM);
+
+        verify(mWallpaperManager, times(1))
+                .setWallpaperComponentWithDescription(mWallpaperDescription,
+                        FLAG_LOCK | FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_LOCK);
+        verify(mWallpaperManager, never()).clear(anyInt());
+    }
+
+    @Test
+    @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    public void testUpdateWallpaperDescription_immediate_systemOnly() throws IOException {
+        mWallpaperBackupAgent.mPackageExists = true;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(
+                new Pair<>(mWallpaperComponent, mWallpaperDescription), /* which */ FLAG_SYSTEM);
+
+        verify(mWallpaperManager, times(1))
+                .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_LOCK);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithDescription(mWallpaperDescription,
+                        FLAG_LOCK | FLAG_SYSTEM);
+        verify(mWallpaperManager, never()).clear(anyInt());
+    }
+
+    @Test
+    @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    public void testUpdateWallpaperDescription_delayed_systemAndLock()
+            throws IOException {
+        mWallpaperBackupAgent.mIsDeviceInRestore = true;
+        mWallpaperBackupAgent.updateWallpaperComponent(
+                new Pair<>(mWallpaperComponent, mWallpaperDescription), /* which */
+                FLAG_LOCK | FLAG_SYSTEM);
+
+        // Imitate wallpaper component installation.
+        mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+                /* uid */0);
+        verify(mWallpaperManager, times(1))
+                .setWallpaperComponentWithDescription(mWallpaperDescription,
+                        FLAG_LOCK | FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_LOCK);
+        verify(mWallpaperManager, never()).clear(anyInt());
+    }
+
+    @Test
+    @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    public void testUpdateWallpaperDescription_delayed_systemOnly() throws IOException {
+        mWallpaperBackupAgent.mIsDeviceInRestore = true;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(
+                new Pair<>(mWallpaperComponent, mWallpaperDescription), /* which */ FLAG_SYSTEM);
+
+        // Imitate wallpaper component installation.
+        mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+                /* uid */0);
+
+        verify(mWallpaperManager, times(1))
+                .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_SYSTEM);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithDescription(mWallpaperDescription, FLAG_LOCK);
+        verify(mWallpaperManager, never())
+                .setWallpaperComponentWithDescription(mWallpaperDescription,
+                        FLAG_LOCK | FLAG_SYSTEM);
+        verify(mWallpaperManager, never()).clear(anyInt());
+    }
+
+    @Test
+    public void testUpdateWallpaperComponent_delayed_systemAndLock() throws IOException {
+        mWallpaperBackupAgent.mIsDeviceInRestore = true;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
@@ -384,13 +514,12 @@
     }
 
     @Test
-    public void testUpdateWallpaperComponent_systemOnly()
+    public void testUpdateWallpaperComponent_delayed_systemOnly()
             throws IOException {
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
 
-        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+        mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
                 /* which */ FLAG_SYSTEM);
-
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
@@ -405,11 +534,11 @@
     }
 
     @Test
-    public void testUpdateWallpaperComponent_deviceNotInRestore_doesNotApply()
+    public void testUpdateWallpaperComponent_delayed_deviceNotInRestore_doesNotApply()
             throws IOException {
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
 
-        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+        mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
                 /* which */ FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
@@ -421,11 +550,11 @@
     }
 
     @Test
-    public void testUpdateWallpaperComponent_differentPackageInstalled_doesNotApply()
+    public void testUpdateWallpaperComponent_delayed_differentPackageInstalled_doesNotApply()
             throws IOException {
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
 
-        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+        mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
                 /* which */ FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate "wrong" wallpaper component installation.
@@ -745,9 +874,8 @@
     }
 
     @Test
-    public void testUpdateWallpaperComponent_delayedRestore_logsSuccess() throws Exception {
+    public void testUpdateWallpaperComponent_delayed_succeeds_logsSuccess() throws Exception {
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
-        when(mWallpaperManager.setWallpaperComponent(any())).thenReturn(true);
         when(mWallpaperManager.setWallpaperComponentWithFlags(any(), eq(FLAG_LOCK | FLAG_SYSTEM)))
                 .thenReturn(true);
         BackupRestoreEventLogger logger = new BackupRestoreEventLogger(
@@ -755,7 +883,7 @@
         when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger);
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
-        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+        mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
                 /* which */ FLAG_LOCK | FLAG_SYSTEM);
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -771,15 +899,41 @@
 
 
     @Test
-    public void testUpdateWallpaperComponent_delayedRestoreFails_logsFailure() throws Exception {
+    @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    public void testUpdateWallpaperDescription_delayed_succeeds_logsSuccess() throws Exception {
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
-        when(mWallpaperManager.setWallpaperComponent(any())).thenReturn(false);
+        when(mWallpaperManager.setWallpaperComponentWithDescription(any(),
+                eq(FLAG_LOCK | FLAG_SYSTEM))).thenReturn(true);
         BackupRestoreEventLogger logger = new BackupRestoreEventLogger(
                 BackupAnnotations.OperationType.RESTORE);
         when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger);
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
-        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+        mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(null, mWallpaperDescription),
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
+        // Imitate wallpaper component installation.
+        mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+                /* uid */0);
+
+        DataTypeResult system = getLoggingResult(WALLPAPER_DESCRIPTION_SYSTEM,
+                logger.getLoggingResults());
+        DataTypeResult lock = getLoggingResult(WALLPAPER_DESCRIPTION_LOCK,
+                logger.getLoggingResults());
+        assertThat(system).isNotNull();
+        assertThat(system.getSuccessCount()).isEqualTo(1);
+        assertThat(lock).isNotNull();
+        assertThat(lock.getSuccessCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testUpdateWallpaperComponent_delayed_fails_logsFailure() throws Exception {
+        mWallpaperBackupAgent.mIsDeviceInRestore = true;
+        BackupRestoreEventLogger logger = new BackupRestoreEventLogger(
+                BackupAnnotations.OperationType.RESTORE);
+        when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger);
+        mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
+
+        mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
                 /* which */ FLAG_LOCK | FLAG_SYSTEM);
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -793,7 +947,29 @@
     }
 
     @Test
-    public void testUpdateWallpaperComponent_delayedRestore_packageNotInstalled_logsFailure()
+    @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    public void testUpdateWallpaperDescription_delayed_fails_logsFailure() throws Exception {
+        mWallpaperBackupAgent.mIsDeviceInRestore = true;
+        BackupRestoreEventLogger logger = new BackupRestoreEventLogger(
+                BackupAnnotations.OperationType.RESTORE);
+        when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger);
+        mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
+
+        mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(null, mWallpaperDescription),
+                /* which */ FLAG_LOCK | FLAG_SYSTEM);
+        // Imitate wallpaper component installation.
+        mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+                /* uid */0);
+
+        DataTypeResult system = getLoggingResult(WALLPAPER_LIVE_SYSTEM, logger.getLoggingResults());
+        assertThat(system).isNotNull();
+        assertThat(system.getFailCount()).isEqualTo(1);
+        assertThat(system.getErrors()).containsKey(
+                WallpaperEventLogger.ERROR_SET_DESCRIPTION_EXCEPTION);
+    }
+
+    @Test
+    public void testUpdateWallpaperComponent_delayed_packageNotInstalled_logsFailure()
             throws Exception {
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
         BackupRestoreEventLogger logger = new BackupRestoreEventLogger(
@@ -801,7 +977,7 @@
         when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger);
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
-        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+        mWallpaperBackupAgent.updateWallpaperComponent(new Pair<>(mWallpaperComponent, null),
                 /* which */ FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
@@ -990,6 +1166,8 @@
         List<File> mBackedUpFiles = new ArrayList<>();
         PackageMonitor mWallpaperPackageMonitor;
         boolean mIsDeviceInRestore = false;
+        boolean mPackageExists = false;
+        int mGetPackageMonitorCallCount = 0;
 
         @Override
         protected void backupFile(File file, FullBackupDataOutput data) {
@@ -998,7 +1176,7 @@
 
         @Override
         boolean servicePackageExists(ComponentName comp) {
-            return false;
+            return mPackageExists;
         }
 
         @Override
@@ -1007,8 +1185,11 @@
         }
 
         @Override
-        PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) {
-            mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, which);
+        PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
+                WallpaperDescription description, int which) {
+            mGetPackageMonitorCallCount++;
+            mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, description,
+                    which);
             return mWallpaperPackageMonitor;
         }
 
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java
index 383bf2f..09aa23e 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java
@@ -16,6 +16,10 @@
 
 package com.android.wallpaperbackup;
 
+import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
+
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_DESCRIPTION_LOCK;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_DESCRIPTION_SYSTEM;
 import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_LOCK;
 import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYSTEM;
 import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK;
@@ -31,16 +35,20 @@
 import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupRestoreEventLogger;
+import android.app.wallpaper.WallpaperDescription;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.wallpaper.WallpaperService;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -63,6 +71,10 @@
 
     private WallpaperEventLogger mWallpaperEventLogger;
     private WallpaperInfo mWallpaperInfo;
+    private WallpaperDescription mWallpaperDescription;
+
+    @Rule
+    public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() throws Exception {
@@ -73,6 +85,8 @@
 
         mWallpaperInfo = getWallpaperInfo();
         mWallpaperEventLogger = new WallpaperEventLogger(mMockBackupManager, mMockBackupAgent);
+        mWallpaperDescription = new WallpaperDescription.Builder().setComponent(
+                mWallpaperInfo.getComponent()).build();
     }
 
     @Test
@@ -263,6 +277,19 @@
     }
 
     @Test
+    @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    public void onSystemLiveWallpaperRestoredWithDescription_logsSuccess() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onSystemLiveWallpaperRestoredWithDescription(mWallpaperDescription);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(
+                WALLPAPER_DESCRIPTION_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
+    }
+
+    @Test
     public void onLockLiveWallpaperRestored_logsSuccess() {
         setUpLoggerForRestore();
 
@@ -274,6 +301,17 @@
     }
 
     @Test
+    public void onLockLiveWallpaperRestoredWithDescription_logsSuccess() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onLockLiveWallpaperRestoredWithDescription(mWallpaperDescription);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_DESCRIPTION_LOCK);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
+    }
+
+    @Test
     public void onImgWallpaperRestored_nullInfo_doesNotLogMetadata() {
         setUpLoggerForRestore();
 
@@ -286,7 +324,7 @@
 
 
     @Test
-    public void onLiveWallpaperRestored_logsMetadata() {
+    public void onSystemLiveWallpaperRestored_logsMetadata() {
         setUpLoggerForRestore();
 
         mWallpaperEventLogger.onSystemLiveWallpaperRestored(mWallpaperInfo.getComponent());
@@ -296,6 +334,19 @@
         assertThat(result.getMetadataHash()).isNotNull();
     }
 
+    @Test
+    @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    public void onSystemLiveWallpaperRestoredDescription_logsMetadata() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onSystemLiveWallpaperRestoredWithDescription(mWallpaperDescription);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(
+                WALLPAPER_DESCRIPTION_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getMetadataHash()).isNotNull();
+    }
+
 
     @Test
     public void onSystemImgWallpaperRestoreFailed_logsFail() {
@@ -373,7 +424,7 @@
     }
 
     @Test
-    public void onWallpaperRestoreException_liveTypeProcessed_doesNotLogForSameImgType() {
+    public void onSystemWallpaperRestoreException_liveTypeProcessed_doesNotLogForSameImgType() {
         setUpLoggerForRestore();
         mWallpaperEventLogger.onSystemLiveWallpaperRestored(mWallpaperInfo.getComponent());
 
@@ -383,6 +434,41 @@
         assertThat(result).isNull();
     }
 
+    @Test
+    @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    public void onSystemWallpaperRestoreException_descriptionProcessed_doesNotLogForSameImgType() {
+        setUpLoggerForRestore();
+        mWallpaperEventLogger.onSystemLiveWallpaperRestoredWithDescription(mWallpaperDescription);
+
+        mWallpaperEventLogger.onRestoreException(new Exception());
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void onLockWallpaperRestoreException_liveTypeProcessed_doesNotLogForSameImgType() {
+        setUpLoggerForRestore();
+        mWallpaperEventLogger.onLockLiveWallpaperRestored(mWallpaperInfo.getComponent());
+
+        mWallpaperEventLogger.onRestoreException(new Exception());
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    @EnableFlags(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    public void onLockWallpaperRestoreException_descriptionProcessed_doesNotLogForSameImgType() {
+        setUpLoggerForRestore();
+        mWallpaperEventLogger.onLockLiveWallpaperRestoredWithDescription(mWallpaperDescription);
+
+        mWallpaperEventLogger.onRestoreException(new Exception());
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK);
+
+        assertThat(result).isNull();
+    }
+
     private BackupRestoreEventLogger.DataTypeResult getLogsForType(String dataType) {
         for (BackupRestoreEventLogger.DataTypeResult result :  mEventLogger.getLoggingResults()) {
             if ((result.getDataType()).equals(dataType)) {
diff --git a/ravenwood/tools/hoststubgen/Android.bp b/ravenwood/tools/hoststubgen/Android.bp
index 004834e..e605318 100644
--- a/ravenwood/tools/hoststubgen/Android.bp
+++ b/ravenwood/tools/hoststubgen/Android.bp
@@ -99,6 +99,7 @@
         "ow2-asm-commons",
         "ow2-asm-tree",
         "ow2-asm-util",
+        "apache-commons-compress",
     ],
 }
 
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt
index b2af782..a62f66d 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Utils.kt
@@ -16,6 +16,15 @@
 package com.android.hoststubgen
 
 import java.io.PrintWriter
+import java.util.zip.CRC32
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
+import org.apache.commons.compress.archivers.zip.ZipFile
+
+/**
+ * Whether to skip compression when adding processed entries back to a zip file.
+ */
+private const val SKIP_COMPRESSION = false
 
 /**
  * Name of this executable. Set it in the main method.
@@ -118,3 +127,29 @@
 
     System.exit(if (success) 0 else 1 )
 }
+
+/**
+ * Copy a single ZIP entry to the output.
+ */
+fun copyZipEntry(
+    inZip: ZipFile,
+    entry: ZipArchiveEntry,
+    out: ZipArchiveOutputStream,
+) {
+    inZip.getRawInputStream(entry).use { out.addRawArchiveEntry(entry, it) }
+}
+
+/**
+ * Add a single ZIP entry with data.
+ */
+fun ZipArchiveOutputStream.addBytesEntry(name: String, data: ByteArray) {
+    val newEntry = ZipArchiveEntry(name)
+    if (SKIP_COMPRESSION) {
+        newEntry.method = 0
+        newEntry.size = data.size.toLong()
+        newEntry.crc = CRC32().apply { update(data) }.value
+    }
+    putArchiveEntry(newEntry)
+    write(data)
+    closeArchiveEntry()
+}
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt
index e2647eb..40d343a 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/ClassNodes.kt
@@ -18,21 +18,20 @@
 import com.android.hoststubgen.ClassParseException
 import com.android.hoststubgen.InvalidJarFileException
 import com.android.hoststubgen.log
-import org.objectweb.asm.ClassReader
-import org.objectweb.asm.tree.AnnotationNode
-import org.objectweb.asm.tree.ClassNode
-import org.objectweb.asm.tree.FieldNode
-import org.objectweb.asm.tree.MethodNode
-import org.objectweb.asm.tree.TypeAnnotationNode
-import java.io.BufferedInputStream
 import java.io.PrintWriter
 import java.util.Arrays
 import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.atomic.AtomicReference
 import java.util.function.Consumer
-import java.util.zip.ZipEntry
-import java.util.zip.ZipFile
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
+import org.apache.commons.compress.archivers.zip.ZipFile
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.tree.AnnotationNode
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.FieldNode
+import org.objectweb.asm.tree.MethodNode
+import org.objectweb.asm.tree.TypeAnnotationNode
 
 /**
  * Stores all classes loaded from a jar file, in a form of [ClassNode]
@@ -189,7 +188,8 @@
          * Load all the classes, without code.
          */
         fun loadClassStructures(
-            inJar: String,
+            inJar: ZipFile,
+            jarName: String,
             timeCollector: Consumer<Double>? = null,
         ): ClassNodes {
             val allClasses = ClassNodes()
@@ -201,20 +201,19 @@
             val exception = AtomicReference<Throwable>()
 
             // Called on a BG thread. Read a single jar entry and add it to [allClasses].
-            fun parseClass(inZip: ZipFile, entry: ZipEntry) {
+            fun parseClass(inZip: ZipFile, entry: ZipArchiveEntry) {
                 try {
-                    inZip.getInputStream(entry).use { ins ->
-                        val cr = ClassReader(BufferedInputStream(ins))
-                        val cn = ClassNode()
-                        cr.accept(
-                            cn, ClassReader.SKIP_CODE
-                                    or ClassReader.SKIP_DEBUG
-                                    or ClassReader.SKIP_FRAMES
-                        )
-                        synchronized(allClasses) {
-                            if (!allClasses.addClass(cn)) {
-                                log.w("Duplicate class found: ${cn.name}")
-                            }
+                    val classBytes = inZip.getInputStream(entry).use { it.readAllBytes() }
+                    val cr = ClassReader(classBytes)
+                    val cn = ClassNode()
+                    cr.accept(
+                        cn, ClassReader.SKIP_CODE
+                                or ClassReader.SKIP_DEBUG
+                                or ClassReader.SKIP_FRAMES
+                    )
+                    synchronized(allClasses) {
+                        if (!allClasses.addClass(cn)) {
+                            log.w("Duplicate class found: ${cn.name}")
                         }
                     }
                 } catch (e: Throwable) {
@@ -224,35 +223,30 @@
             }
 
             // Actually open the jar and read it on worker threads.
-            val time = log.iTime("Reading class structure from $inJar") {
+            val time = log.iTime("Reading class structure from $jarName") {
                 log.withIndent {
-                    ZipFile(inJar).use { inZip ->
-                        val inEntries = inZip.entries()
-
-                        while (inEntries.hasMoreElements()) {
-                            val entry = inEntries.nextElement()
-
-                            if (entry.name.endsWith(".class")) {
-                                executor.submit {
-                                    parseClass(inZip, entry)
-                                }
-                            } else if (entry.name.endsWith(".dex")) {
-                                // Seems like it's an ART jar file. We can't process it.
-                                // It's a fatal error.
-                                throw InvalidJarFileException(
-                                    "$inJar is not a desktop jar file."
-                                            + " It contains a *.dex file."
-                                )
-                            } else {
-                                // Unknown file type. Skip.
+                    inJar.entries.asSequence().forEach { entry ->
+                        if (entry.name.endsWith(".class")) {
+                            executor.submit {
+                                parseClass(inJar, entry)
                             }
+                        } else if (entry.name.endsWith(".dex")) {
+                            // Seems like it's an ART jar file. We can't process it.
+                            // It's a fatal error.
+                            throw InvalidJarFileException(
+                                "$jarName is not a desktop jar file."
+                                        + " It contains a *.dex file."
+                            )
+                        } else {
+                            // Unknown file type. Skip.
                         }
-                        // Wait for all the work to complete. (must do it before closing the zip)
-                        log.i("Waiting for all loaders to finish...")
-                        executor.shutdown()
-                        executor.awaitTermination(5, TimeUnit.MINUTES)
-                        log.i("All loaders to finished.")
                     }
+
+                    // Wait for all the work to complete. (must do it before closing the zip)
+                    log.i("Waiting for all loaders to finish...")
+                    executor.shutdown()
+                    executor.awaitTermination(5, TimeUnit.MINUTES)
+                    log.i("All loaders to finished.")
                 }
 
                 // If any exception is detected, throw it.
@@ -261,13 +255,13 @@
                 }
 
                 if (allClasses.size == 0) {
-                    log.w("$inJar contains no *.class files.")
+                    log.w("$jarName contains no *.class files.")
                 } else {
-                    log.i("Loaded ${allClasses.size} classes from $inJar.")
+                    log.i("Loaded ${allClasses.size} classes from $jarName.")
                 }
             }
             timeCollector?.accept(time)
             return allClasses
         }
     }
-}
\ No newline at end of file
+}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 3335405..2edcb2a 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -19,13 +19,11 @@
 import com.android.hoststubgen.dumper.ApiDumper
 import com.android.hoststubgen.filters.FilterPolicy
 import com.android.hoststubgen.filters.printAsTextPolicy
-import java.io.BufferedInputStream
-import java.io.BufferedOutputStream
 import java.io.FileOutputStream
 import java.io.PrintWriter
-import java.util.zip.ZipEntry
-import java.util.zip.ZipFile
-import java.util.zip.ZipOutputStream
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
+import org.apache.commons.compress.archivers.zip.ZipFile
 
 /**
  * Actual main class.
@@ -34,9 +32,10 @@
     fun run() {
         val errors = HostStubGenErrors()
         val stats = HostStubGenStats()
+        val inJar = ZipFile(options.inJar.get)
 
         // Load all classes.
-        val allClasses = ClassNodes.loadClassStructures(options.inJar.get)
+        val allClasses = ClassNodes.loadClassStructures(inJar, options.inJar.get)
 
         // Dump the classes, if specified.
         options.inputJarDumpFile.ifSet {
@@ -59,7 +58,7 @@
         val processor = HostStubGenClassProcessor(options, allClasses, errors, stats)
 
         // Transform the jar.
-        convert(
+        inJar.convert(
             options.inJar.get,
             options.outJar.get,
             processor,
@@ -88,7 +87,7 @@
     /**
      * Convert a JAR file into "stub" and "impl" JAR files.
      */
-    private fun convert(
+    private fun ZipFile.convert(
         inJar: String,
         outJar: String?,
         processor: HostStubGenClassProcessor,
@@ -100,45 +99,39 @@
         log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled")
 
         log.iTime("Transforming jar") {
-            var itemIndex = 0
             var numItemsProcessed = 0
             var numItems = -1 // == Unknown
 
             log.withIndent {
-                // Open the input jar file and process each entry.
-                ZipFile(inJar).use { inZip ->
+                val entries = entries.toList()
 
-                    numItems = inZip.size()
-                    val shardStart = numItems * shard / numShards
-                    val shardNextStart = numItems * (shard + 1) / numShards
+                numItems = entries.size
+                val shardStart = numItems * shard / numShards
+                val shardNextStart = numItems * (shard + 1) / numShards
 
-                    maybeWithZipOutputStream(outJar) { outStream ->
-                        val inEntries = inZip.entries()
-                        while (inEntries.hasMoreElements()) {
-                            val entry = inEntries.nextElement()
-                            val inShard = (shardStart <= itemIndex)
-                                    && (itemIndex < shardNextStart)
-                            itemIndex++
-                            if (!inShard) {
-                                continue
-                            }
-                            convertSingleEntry(inZip, entry, outStream, processor)
-                            numItemsProcessed++
+                maybeWithZipOutputStream(outJar) { outStream ->
+                    entries.forEachIndexed { itemIndex, entry ->
+                        val inShard = (shardStart <= itemIndex)
+                                && (itemIndex < shardNextStart)
+                        if (!inShard) {
+                            return@forEachIndexed
                         }
-                        log.i("Converted all entries.")
+                        convertSingleEntry(this, entry, outStream, processor)
+                        numItemsProcessed++
                     }
-                    outJar?.let { log.i("Created: $it") }
+                    log.i("Converted all entries.")
                 }
+                outJar?.let { log.i("Created: $it") }
             }
             log.i("%d / %d item(s) processed.", numItemsProcessed, numItems)
         }
     }
 
-    private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T {
+    private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipArchiveOutputStream?) -> T): T {
         if (filename == null) {
             return block(null)
         }
-        return ZipOutputStream(BufferedOutputStream(FileOutputStream(filename))).use(block)
+        return ZipArchiveOutputStream(FileOutputStream(filename).buffered()).use(block)
     }
 
     /**
@@ -146,8 +139,8 @@
      */
     private fun convertSingleEntry(
         inZip: ZipFile,
-        entry: ZipEntry,
-        outStream: ZipOutputStream?,
+        entry: ZipArchiveEntry,
+        outStream: ZipArchiveOutputStream?,
         processor: HostStubGenClassProcessor
     ) {
         log.d("Entry: %s", entry.name)
@@ -181,32 +174,12 @@
     }
 
     /**
-     * Copy a single ZIP entry to the output.
-     */
-    private fun copyZipEntry(
-        inZip: ZipFile,
-        entry: ZipEntry,
-        out: ZipOutputStream,
-    ) {
-        // TODO: It seems like copying entries this way is _very_ slow,
-        // even with out.setLevel(0). Look for other ways to do it.
-
-        inZip.getInputStream(entry).use { ins ->
-            // Copy unknown entries as is to the impl out. (but not to the stub out.)
-            val outEntry = ZipEntry(entry.name)
-            out.putNextEntry(outEntry)
-            ins.transferTo(out)
-            out.closeEntry()
-        }
-    }
-
-    /**
      * Convert a single class.
      */
     private fun processSingleClass(
         inZip: ZipFile,
-        entry: ZipEntry,
-        outStream: ZipOutputStream?,
+        entry: ZipArchiveEntry,
+        outStream: ZipArchiveOutputStream?,
         processor: HostStubGenClassProcessor
     ) {
         val classInternalName = entry.name.replaceFirst("\\.class$".toRegex(), "")
@@ -227,12 +200,10 @@
         if (outStream != null) {
             log.v("Creating class: %s Policy: %s", classInternalName, classPolicy)
             log.withIndent {
-                BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
-                    val newEntry = ZipEntry(newName)
-                    outStream.putNextEntry(newEntry)
-                    val classBytecode = bis.readAllBytes()
-                    outStream.write(processor.processClassBytecode(classBytecode))
-                    outStream.closeEntry()
+                inZip.getInputStream(entry).use { zis ->
+                    var classBytecode = zis.readAllBytes()
+                    classBytecode = processor.processClassBytecode(classBytecode)
+                    outStream.addBytesEntry(newName, classBytecode)
                 }
             }
         }
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
index 04e3bda2..8e36323 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -17,17 +17,17 @@
 
 import com.android.hoststubgen.GeneralUserErrorException
 import com.android.hoststubgen.HostStubGenClassProcessor
+import com.android.hoststubgen.addBytesEntry
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.zipEntryNameToClassName
+import com.android.hoststubgen.copyZipEntry
 import com.android.hoststubgen.executableName
 import com.android.hoststubgen.log
 import com.android.platform.test.ravenwood.ravenizer.adapter.RunnerRewritingAdapter
-import java.io.BufferedInputStream
-import java.io.BufferedOutputStream
 import java.io.FileOutputStream
-import java.util.zip.ZipEntry
-import java.util.zip.ZipFile
-import java.util.zip.ZipOutputStream
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
+import org.apache.commons.compress.archivers.zip.ZipFile
 import org.objectweb.asm.ClassReader
 import org.objectweb.asm.ClassVisitor
 import org.objectweb.asm.ClassWriter
@@ -93,13 +93,14 @@
         val stats = RavenizerStats()
 
         stats.totalTime = log.nTime {
-            val allClasses = ClassNodes.loadClassStructures(options.inJar.get) {
+            val inJar = ZipFile(options.inJar.get)
+            val allClasses = ClassNodes.loadClassStructures(inJar, options.inJar.get) {
                 stats.loadStructureTime = it
             }
             val processor = HostStubGenClassProcessor(options, allClasses)
 
-            process(
-                options.inJar.get,
+            inJar.process(
+                options.outJar.get,
                 options.outJar.get,
                 options.enableValidation.get,
                 options.fatalValidation.get,
@@ -111,7 +112,7 @@
         log.i(stats.toString())
     }
 
-    private fun process(
+    private fun ZipFile.process(
         inJar: String,
         outJar: String,
         enableValidation: Boolean,
@@ -138,40 +139,34 @@
         }
 
         stats.totalProcessTime = log.vTime("$executableName processing $inJar") {
-            ZipFile(inJar).use { inZip ->
-                val inEntries = inZip.entries()
+            ZipArchiveOutputStream(FileOutputStream(outJar).buffered()).use { outZip ->
+                entries.asSequence().forEach { entry ->
+                    stats.totalEntries++
+                    if (entry.name.endsWith(".dex")) {
+                        // Seems like it's an ART jar file. We can't process it.
+                        // It's a fatal error.
+                        throw GeneralUserErrorException(
+                            "$inJar is not a desktop jar file. It contains a *.dex file."
+                        )
+                    }
 
-                stats.totalEntries = inZip.size()
+                    if (stripMockito && entry.name.isMockitoFile()) {
+                        // Skip this entry
+                        return@forEach
+                    }
 
-                ZipOutputStream(BufferedOutputStream(FileOutputStream(outJar))).use { outZip ->
-                    while (inEntries.hasMoreElements()) {
-                        val entry = inEntries.nextElement()
+                    val className = zipEntryNameToClassName(entry.name)
 
-                        if (entry.name.endsWith(".dex")) {
-                            // Seems like it's an ART jar file. We can't process it.
-                            // It's a fatal error.
-                            throw GeneralUserErrorException(
-                                "$inJar is not a desktop jar file. It contains a *.dex file."
-                            )
-                        }
+                    if (className != null) {
+                        stats.totalClasses += 1
+                    }
 
-                        if (stripMockito && entry.name.isMockitoFile()) {
-                            // Skip this entry
-                            continue
-                        }
-
-                        val className = zipEntryNameToClassName(entry.name)
-
-                        if (className != null) {
-                            stats.totalClasses += 1
-                        }
-
-                        if (className != null &&
-                            shouldProcessClass(processor.allClasses, className)) {
-                            processSingleClass(inZip, entry, outZip, processor, stats)
-                        } else {
-                            // Too slow, let's use merge_zips to bring back the original classes.
-                            copyZipEntry(inZip, entry, outZip, stats)
+                    if (className != null &&
+                        shouldProcessClass(processor.allClasses, className)) {
+                        processSingleClass(this, entry, outZip, processor, stats)
+                    } else {
+                        stats.totalCopyTime += log.nTime {
+                            copyZipEntry(this, entry, outZip)
                         }
                     }
                 }
@@ -179,53 +174,25 @@
         }
     }
 
-    /**
-     * Copy a single ZIP entry to the output.
-     */
-    private fun copyZipEntry(
-        inZip: ZipFile,
-        entry: ZipEntry,
-        out: ZipOutputStream,
-        stats: RavenizerStats,
-    ) {
-        stats.totalCopyTime += log.nTime {
-            inZip.getInputStream(entry).use { ins ->
-                // Copy unknown entries as is to the impl out. (but not to the stub out.)
-                val outEntry = ZipEntry(entry.name)
-                outEntry.method = 0
-                outEntry.size = entry.size
-                outEntry.crc = entry.crc
-                out.putNextEntry(outEntry)
-
-                ins.transferTo(out)
-
-                out.closeEntry()
-            }
-        }
-    }
-
     private fun processSingleClass(
         inZip: ZipFile,
-        entry: ZipEntry,
-        outZip: ZipOutputStream,
+        entry: ZipArchiveEntry,
+        outZip: ZipArchiveOutputStream,
         processor: HostStubGenClassProcessor,
         stats: RavenizerStats,
     ) {
         stats.processedClasses += 1
-        val newEntry = ZipEntry(entry.name)
-        outZip.putNextEntry(newEntry)
-
-        BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
-            var classBytes = bis.readBytes()
+        inZip.getInputStream(entry).use { zis ->
+            var classBytes = zis.readAllBytes()
             stats.totalRavenizeTime += log.vTime("Ravenize ${entry.name}") {
                 classBytes = ravenizeSingleClass(entry, classBytes, processor.allClasses)
             }
             stats.totalHostStubGenTime += log.vTime("HostStubGen ${entry.name}") {
                 classBytes = processor.processClassBytecode(classBytes)
             }
-            outZip.write(classBytes)
+            // TODO: if the class does not change, use copyZipEntry
+            outZip.addBytesEntry(entry.name, classBytes)
         }
-        outZip.closeEntry()
     }
 
     /**
@@ -237,7 +204,7 @@
     }
 
     private fun ravenizeSingleClass(
-        entry: ZipEntry,
+        entry: ZipArchiveEntry,
         input: ByteArray,
         allClasses: ClassNodes,
     ): ByteArray {
diff --git a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
index 94cef41..edcf574 100644
--- a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
@@ -17,6 +17,7 @@
 package com.android.server.accessibility;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.app.Notification;
@@ -149,11 +150,7 @@
             }
 
             if (state == TelephonyManager.CALL_STATE_IDLE) {
-                if (mIsCommDeviceChangedRegistered) {
-                    mIsCommDeviceChangedRegistered = false;
-                    mAudioManager.removeOnCommunicationDeviceChangedListener(
-                            mCommDeviceChangedListener);
-                }
+                removeOnCommunicationDeviceChangedListenerIfNeeded(mCommDeviceChangedListener);
                 dismissNotificationIfNeeded();
 
                 if (mHearingDevice != null) {
@@ -172,10 +169,8 @@
                     if (mHearingDevice != null) {
                         showNotificationIfNeeded();
                     } else {
-                        mAudioManager.addOnCommunicationDeviceChangedListener(
-                                mCommDeviceChangedExecutor,
+                        addOnCommunicationDeviceChangedListenerIfNeeded(mCommDeviceChangedExecutor,
                                 mCommDeviceChangedListener);
-                        mIsCommDeviceChangedRegistered = true;
                     }
                 } else {
                     mHearingDevice = getSupportedInputHearingDeviceInfo(
@@ -187,6 +182,27 @@
             }
         }
 
+        private void addOnCommunicationDeviceChangedListenerIfNeeded(
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull AudioManager.OnCommunicationDeviceChangedListener listener) {
+            if (mIsCommDeviceChangedRegistered) {
+                return;
+            }
+
+            mIsCommDeviceChangedRegistered = true;
+            mAudioManager.addOnCommunicationDeviceChangedListener(executor, listener);
+        }
+
+        private void removeOnCommunicationDeviceChangedListenerIfNeeded(
+                @NonNull AudioManager.OnCommunicationDeviceChangedListener listener) {
+            if (!mIsCommDeviceChangedRegistered) {
+                return;
+            }
+
+            mAudioManager.removeOnCommunicationDeviceChangedListener(listener);
+            mIsCommDeviceChangedRegistered = false;
+        }
+
         private void showNotificationIfNeeded() {
             if (mIsNotificationShown) {
                 return;
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 658ea4c..193d827 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -168,7 +168,6 @@
     private AdbConnectionInfo mAdbConnectionInfo = new AdbConnectionInfo();
     // Polls for a tls port property when adb wifi is enabled
     private AdbConnectionPortPoller mConnectionPortPoller;
-    private final PortListenerImpl mPortListener = new PortListenerImpl();
     private final Ticker mTicker;
 
     public AdbDebuggingManager(Context context) {
@@ -323,10 +322,6 @@
         }
     }
 
-    interface AdbConnectionPortListener {
-        void onPortReceived(int port);
-    }
-
     /**
      * This class will poll for a period of time for adbd to write the port
      * it connected to.
@@ -336,16 +331,11 @@
      * port through different means. A better fix would be to always start AdbDebuggingManager, but
      * it needs to adjust accordingly on whether ro.adb.secure is set.
      */
-    static class AdbConnectionPortPoller extends Thread {
+    private class AdbConnectionPortPoller extends Thread {
         private final String mAdbPortProp = "service.adb.tls.port";
-        private AdbConnectionPortListener mListener;
         private final int mDurationSecs = 10;
         private AtomicBoolean mCanceled = new AtomicBoolean(false);
 
-        AdbConnectionPortPoller(AdbConnectionPortListener listener) {
-            mListener = listener;
-        }
-
         @Override
         public void run() {
             Slog.d(TAG, "Starting adb port property poller");
@@ -362,13 +352,22 @@
                 // to start the server. Otherwise we should have a valid port.
                 int port = SystemProperties.getInt(mAdbPortProp, Integer.MAX_VALUE);
                 if (port == -1 || (port > 0 && port <= 65535)) {
-                    mListener.onPortReceived(port);
+                    onPortReceived(port);
                     return;
                 }
                 SystemClock.sleep(1000);
             }
             Slog.w(TAG, "Failed to receive adb connection port");
-            mListener.onPortReceived(-1);
+            onPortReceived(-1);
+        }
+
+        private void onPortReceived(int port) {
+            Slog.d(TAG, "Received tls port=" + port);
+            Message msg = mHandler.obtainMessage(port > 0
+                    ? AdbDebuggingHandler.MSG_SERVER_CONNECTED
+                    : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED);
+            msg.obj = port;
+            mHandler.sendMessage(msg);
         }
 
         public void cancelAndWait() {
@@ -382,17 +381,6 @@
         }
     }
 
-    class PortListenerImpl implements AdbConnectionPortListener {
-        public void onPortReceived(int port) {
-            Slog.d(TAG, "Received tls port=" + port);
-            Message msg = mHandler.obtainMessage(port > 0
-                     ? AdbDebuggingHandler.MSG_SERVER_CONNECTED
-                     : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED);
-            msg.obj = port;
-            mHandler.sendMessage(msg);
-        }
-    }
-
     @VisibleForTesting
     static class AdbDebuggingThread extends Thread {
         private boolean mStopped;
@@ -800,7 +788,6 @@
 
         // === Messages we can send to adbd ===========
         static final String MSG_DISCONNECT_DEVICE = "DD";
-        static final String MSG_DISABLE_ADBDWIFI = "DA";
 
         @Nullable @VisibleForTesting AdbKeyStore mAdbKeyStore;
 
@@ -1088,8 +1075,7 @@
                     mContext.registerReceiver(mBroadcastReceiver, intentFilter);
 
                     SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
-                    mConnectionPortPoller =
-                            new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+                    mConnectionPortPoller = new AdbDebuggingManager.AdbConnectionPortPoller();
                     mConnectionPortPoller.start();
 
                     startAdbDebuggingThread();
@@ -1106,9 +1092,6 @@
                     setAdbConnectionInfo(null);
                     mContext.unregisterReceiver(mBroadcastReceiver);
 
-                    if (mThread != null) {
-                        mThread.sendResponse(MSG_DISABLE_ADBDWIFI);
-                    }
                     onAdbdWifiServerDisconnected(-1);
                     stopAdbDebuggingThread();
                     break;
@@ -1138,8 +1121,7 @@
                     mContext.registerReceiver(mBroadcastReceiver, intentFilter);
 
                     SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
-                    mConnectionPortPoller =
-                            new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+                    mConnectionPortPoller = new AdbDebuggingManager.AdbConnectionPortPoller();
                     mConnectionPortPoller.start();
 
                     startAdbDebuggingThread();
@@ -1257,7 +1239,7 @@
                     if (mAdbWifiEnabled) {
                         // In scenarios where adbd is restarted, the tls port may change.
                         mConnectionPortPoller =
-                                new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
+                                new AdbDebuggingManager.AdbConnectionPortPoller();
                         mConnectionPortPoller.start();
                     }
                     break;
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index 40f7c87..d12a0a2 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -21,10 +21,8 @@
 import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
-import android.debug.AdbManager;
 import android.debug.AdbManagerInternal;
 import android.debug.AdbTransportType;
 import android.debug.FingerprintAndPairDevice;
@@ -40,10 +38,8 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.adb.AdbServiceDumpProto;
-import android.sysprop.AdbProperties;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -63,7 +59,6 @@
 import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * The Android Debug Bridge (ADB) service. This controls the availability of ADB and authorization
@@ -85,12 +80,6 @@
      */
     static final String CTL_STOP = "ctl.stop";
 
-    // The tcp port adb is currently using
-    AtomicInteger mConnectionPort = new AtomicInteger(-1);
-
-    private final AdbConnectionPortListener mPortListener = new AdbConnectionPortListener();
-    private AdbDebuggingManager.AdbConnectionPortPoller mConnectionPortPoller;
-
     private final RemoteCallbackList<IAdbCallback> mCallbacks = new RemoteCallbackList<>();
     /**
      * Manages the service lifecycle for {@code AdbService} in {@code SystemServer}.
@@ -404,39 +393,6 @@
         Slog.d(TAG, "Unregistering callback " + callback);
         mCallbacks.unregister(callback);
     }
-    /**
-     * This listener is only used when ro.adb.secure=0. Otherwise, AdbDebuggingManager will
-     * do this.
-     */
-    class AdbConnectionPortListener implements AdbDebuggingManager.AdbConnectionPortListener {
-        public void onPortReceived(int port) {
-            if (port > 0 && port <= 65535) {
-                mConnectionPort.set(port);
-            } else {
-                mConnectionPort.set(-1);
-                // Turn off wifi debugging, since the server did not start.
-                try {
-                    Settings.Global.putInt(mContentResolver,
-                            Settings.Global.ADB_WIFI_ENABLED, 0);
-                } catch (SecurityException e) {
-                    // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't
-                    // be changed.
-                    Slog.d(TAG, "ADB_ENABLED is restricted.");
-                }
-            }
-            broadcastPortInfo(mConnectionPort.get());
-        }
-    }
-
-    private void broadcastPortInfo(int port) {
-        Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
-        intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, (port >= 0)
-                ? AdbManager.WIRELESS_STATUS_CONNECTED
-                : AdbManager.WIRELESS_STATUS_DISCONNECTED);
-        intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
-        AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL);
-        Slog.i(TAG, "sent port broadcast port=" + port);
-    }
 
     private void startAdbd() {
         SystemProperties.set(CTL_START, ADBD);
@@ -470,20 +426,11 @@
         } else if (transportType == AdbTransportType.WIFI && enable != mIsAdbWifiEnabled) {
             mIsAdbWifiEnabled = enable;
             if (mIsAdbWifiEnabled) {
-                if (!AdbProperties.secure().orElse(false)) {
-                    // Start adbd. If this is secure adb, then we defer enabling adb over WiFi.
-                    SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
-                    mConnectionPortPoller =
-                            new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener);
-                    mConnectionPortPoller.start();
-                }
+                // Start adb over WiFi.
+                SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
             } else {
                 // Stop adb over WiFi.
                 SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "0");
-                if (mConnectionPortPoller != null) {
-                    mConnectionPortPoller.cancelAndWait();
-                    mConnectionPortPoller = null;
-                }
             }
         } else {
             // No change
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 517279b..8b3eb48 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -98,6 +98,9 @@
 
     @VisibleForTesting static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;
 
+    @VisibleForTesting
+    static final long APP_START_INFO_HISTORY_LENGTH_MS = TimeUnit.DAYS.toMillis(14);
+
     /**
      * The max number of records that can be present in {@link mInProgressRecords}.
      *
@@ -120,9 +123,13 @@
      * Monotonic clock which does not reset on reboot.
      *
      * Time for offset is persisted along with records, see {@link #persistProcessStartInfo}.
-     * This does not follow the recommendation of {@link MonotonicClock} to persist on shutdown as
-     * it's ok in this case to lose any time change past the last persist as records added since
-     * then will be lost as well and the purpose of this clock is to keep records in order.
+     * This does not currently follow the recommendation of {@link MonotonicClock} to persist on
+     * shutdown as it's ok in this case to lose any time change past the last persist as records
+     * added since then will be lost as well. Since this time is used for cleanup as well, the
+     * potential old offset may result in the cleanup window being extended slightly beyond the
+     * targeted 14 days.
+     *
+     * TODO: b/402794215 - Persist on shutdown once persist performance is sufficiently improved.
      */
     @VisibleForTesting MonotonicClock mMonotonicClock = null;
 
@@ -296,7 +303,7 @@
             if (!mEnabled) {
                 return;
             }
-            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
+            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.setIntent(intent);
             start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
@@ -454,7 +461,7 @@
             if (!mEnabled) {
                 return;
             }
-            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
+            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
             addBaseFieldsFromProcessRecord(start, app);
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.addStartupTimestamp(
@@ -484,7 +491,7 @@
             if (!mEnabled) {
                 return;
             }
-            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
+            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
             addBaseFieldsFromProcessRecord(start, app);
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.addStartupTimestamp(
@@ -511,7 +518,7 @@
             if (!mEnabled) {
                 return;
             }
-            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
+            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
             addBaseFieldsFromProcessRecord(start, app);
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.addStartupTimestamp(
@@ -533,7 +540,7 @@
             if (!mEnabled) {
                 return;
             }
-            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
+            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTimeMs());
             addBaseFieldsFromProcessRecord(start, app);
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.addStartupTimestamp(
@@ -721,8 +728,8 @@
 
                     Collections.sort(
                             list, (a, b) ->
-                            Long.compare(b.getMonoticCreationTimeMs(),
-                                    a.getMonoticCreationTimeMs()));
+                            Long.compare(b.getMonotonicCreationTimeMs(),
+                                    a.getMonotonicCreationTimeMs()));
                     int size = list.size();
                     if (maxNum > 0) {
                         size = Math.min(size, maxNum);
@@ -1098,7 +1105,7 @@
                     mLastAppStartInfoPersistTimestamp = now;
                 }
             }
-            proto.write(AppsStartInfoProto.MONOTONIC_TIME, getMonotonicTime());
+            proto.write(AppsStartInfoProto.MONOTONIC_TIME, getMonotonicTimeMs());
             if (succeeded) {
                 proto.flush();
                 af.finishWrite(out);
@@ -1219,7 +1226,11 @@
         }
     }
 
-    private long getMonotonicTime() {
+    /**
+     * Monotonic time that doesn't change with reboot or device time change for ordering records.
+     */
+    @VisibleForTesting
+    public long getMonotonicTimeMs() {
         if (mMonotonicClock == null) {
             // This should never happen. Return 0 to not interfere with past or future records.
             return 0;
@@ -1229,7 +1240,7 @@
 
     /** A container class of (@link android.app.ApplicationStartInfo) */
     final class AppStartInfoContainer {
-        private ArrayList<ApplicationStartInfo> mInfos; // Always kept sorted by first timestamp.
+        private ArrayList<ApplicationStartInfo> mInfos; // Always kept sorted by monotonic time.
         private int mMaxCapacity;
         private int mUid;
         private boolean mMonitoringModeEnabled = false;
@@ -1260,9 +1271,12 @@
                 return;
             }
 
-            // Sort records so we can remove the least recent ones.
-            Collections.sort(mInfos, (a, b) ->
-                    Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs()));
+            if (!android.app.Flags.appStartInfoKeepRecordsSorted()) {
+                // Sort records so we can remove the least recent ones.
+                Collections.sort(mInfos, (a, b) ->
+                        Long.compare(b.getMonotonicCreationTimeMs(),
+                                a.getMonotonicCreationTimeMs()));
+            }
 
             // Remove records and trim list object back to size.
             mInfos.subList(0, mInfos.size() - getMaxCapacity()).clear();
@@ -1277,25 +1291,34 @@
 
         @GuardedBy("mLock")
         void addStartInfoLocked(ApplicationStartInfo info) {
-            int size = mInfos.size();
-            if (size >= getMaxCapacity()) {
-                // Remove oldest record if size is over max capacity.
-                int oldestIndex = -1;
-                long oldestTimeStamp = Long.MAX_VALUE;
-                for (int i = 0; i < size; i++) {
-                    ApplicationStartInfo startInfo = mInfos.get(i);
-                    if (startInfo.getMonoticCreationTimeMs() < oldestTimeStamp) {
-                        oldestTimeStamp = startInfo.getMonoticCreationTimeMs();
-                        oldestIndex = i;
+            if (android.app.Flags.appStartInfoKeepRecordsSorted()) {
+                while (mInfos.size() >= getMaxCapacity()) {
+                    // Expected to execute at most once.
+                    mInfos.removeLast();
+                }
+                mInfos.addFirst(info);
+            } else {
+                int size = mInfos.size();
+                if (size >= getMaxCapacity()) {
+                    // Remove oldest record if size is over max capacity.
+                    int oldestIndex = -1;
+                    long oldestTimeStamp = Long.MAX_VALUE;
+                    for (int i = 0; i < size; i++) {
+                        ApplicationStartInfo startInfo = mInfos.get(i);
+                        if (startInfo.getMonotonicCreationTimeMs() < oldestTimeStamp) {
+                            oldestTimeStamp = startInfo.getMonotonicCreationTimeMs();
+                            oldestIndex = i;
+                        }
+                    }
+                    if (oldestIndex >= 0) {
+                        mInfos.remove(oldestIndex);
                     }
                 }
-                if (oldestIndex >= 0) {
-                    mInfos.remove(oldestIndex);
-                }
+                mInfos.add(info);
+                Collections.sort(mInfos, (a, b) ->
+                        Long.compare(b.getMonotonicCreationTimeMs(),
+                                a.getMonotonicCreationTimeMs()));
             }
-            mInfos.add(info);
-            Collections.sort(mInfos, (a, b) ->
-                    Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs()));
         }
 
         /**
@@ -1439,9 +1462,25 @@
             long token = proto.start(fieldId);
             proto.write(AppsStartInfoProto.Package.User.UID, mUid);
             int size = mInfos.size();
-            for (int i = 0; i < size; i++) {
-                mInfos.get(i).writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO,
-                        byteArrayOutputStream, objectOutputStream, typedXmlSerializer);
+            if (android.app.Flags.appStartInfoCleanupOldRecords()) {
+                long removeOlderThan = getMonotonicTimeMs() - APP_START_INFO_HISTORY_LENGTH_MS;
+                // Iterate backwards so we can remove old records as we go.
+                for (int i = size - 1; i >= 0; i--) {
+                    if (mInfos.get(i).getMonotonicCreationTimeMs() < removeOlderThan) {
+                        // Remove the record.
+                        mInfos.remove(i);
+                    } else {
+                        mInfos.get(i).writeToProto(
+                                proto, AppsStartInfoProto.Package.User.APP_START_INFO,
+                                byteArrayOutputStream, objectOutputStream, typedXmlSerializer);
+                    }
+                }
+            } else {
+                for (int i = 0; i < size; i++) {
+                    mInfos.get(i).writeToProto(
+                            proto, AppsStartInfoProto.Package.User.APP_START_INFO,
+                            byteArrayOutputStream, objectOutputStream, typedXmlSerializer);
+                }
             }
             proto.write(AppsStartInfoProto.Package.User.MONITORING_ENABLED, mMonitoringModeEnabled);
             proto.end(token);
@@ -1466,7 +1505,13 @@
                         info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO,
                                 byteArrayInputStream, objectInputStream, typedXmlPullParser);
                         info.setPackageName(packageName);
-                        mInfos.add(info);
+                        if (android.app.Flags.appStartInfoKeepRecordsSorted()) {
+                            // Since the writes are done from oldest to newest, each additional
+                            // record will be newer than the previous so use addFirst.
+                            mInfos.addFirst(info);
+                        } else {
+                            mInfos.add(info);
+                        }
                         break;
                     case (int) AppsStartInfoProto.Package.User.MONITORING_ENABLED:
                         mMonitoringModeEnabled = proto.readBoolean(
diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
index 7502664..180ef85 100644
--- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
+++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
@@ -39,6 +39,7 @@
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.IntArray;
+import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.media.permission.INativePermissionController;
@@ -62,6 +63,8 @@
 /** Responsible for synchronizing system server permission state to the native audioserver. */
 public class AudioServerPermissionProvider {
 
+    static final String TAG = "AudioServerPermissionProvider";
+
     static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE];
 
     static final byte[] HDS_PERMS = new byte[] {PermissionEnum.CAPTURE_AUDIO_HOTWORD,
@@ -219,10 +222,13 @@
     public void setIsolatedServiceUid(int uid, int owningUid) {
         synchronized (mLock) {
             if (mHdsUid == uid) return;
-            var packageNameSet = mPackageMap.get(owningUid);
-            if (packageNameSet == null) return;
-            var packageName = packageNameSet.iterator().next();
-            onModifyPackageState(uid, packageName, /* isRemove= */ false);
+            var packageNameSet = mPackageMap.get(UserHandle.getAppId(owningUid));
+            if (packageNameSet != null) {
+                var packageName = packageNameSet.iterator().next();
+                onModifyPackageState(uid, packageName, /* isRemove= */ false);
+            } else {
+                Log.wtf(TAG, "setIsolatedService owning uid not found");
+            }
             // permissions
             mHdsUid = uid;
             if (mDest == null) {
@@ -249,11 +255,19 @@
 
     public void clearIsolatedServiceUid(int uid) {
         synchronized (mLock) {
-            if (mHdsUid != uid) return;
-            var packageNameSet = mPackageMap.get(uid);
-            if (packageNameSet == null) return;
-            var packageName = packageNameSet.iterator().next();
-            onModifyPackageState(uid, packageName, /* isRemove= */ true);
+            var packageNameSet = mPackageMap.get(UserHandle.getAppId(uid));
+            if (mHdsUid != uid) {
+                Log.wtf(TAG,
+                        "Unexpected isolated service uid cleared: " + uid + packageNameSet
+                                + ", expected " + mHdsUid);
+                return;
+            }
+            if (packageNameSet != null) {
+                var packageName = packageNameSet.iterator().next();
+                onModifyPackageState(uid, packageName, /* isRemove= */ true);
+            } else {
+                Log.wtf(TAG, "clearIsolatedService uid not found");
+            }
             // permissions
             if (mDest == null) {
                 mIsUpdateDeferred = true;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bf7f194..6b3661a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -929,7 +929,8 @@
     private final Object mAbsoluteVolumeDeviceInfoMapLock = new Object();
     // Devices where the framework sends a full scale audio signal, and controls the volume of
     // the external audio system separately.
-    // For possible volume behaviors, see {@link AudioManager.AbsoluteDeviceVolumeBehavior}.
+    // For possible volume behaviors, see
+    // {@link AudioDeviceVolumeManager.AbsoluteDeviceVolumeBehavior}.
     @GuardedBy("mAbsoluteVolumeDeviceInfoMapLock")
     Map<Integer, AbsoluteVolumeDeviceInfo> mAbsoluteVolumeDeviceInfoMap = new ArrayMap<>();
 
@@ -942,7 +943,7 @@
         private final List<VolumeInfo> mVolumeInfos;
         private final IAudioDeviceVolumeDispatcher mCallback;
         private final boolean mHandlesVolumeAdjustment;
-        private @AudioManager.AbsoluteDeviceVolumeBehavior int mDeviceVolumeBehavior;
+        private @AudioDeviceVolumeManager.AbsoluteDeviceVolumeBehavior int mDeviceVolumeBehavior;
 
         private AbsoluteVolumeDeviceInfo(
                 AudioService parent,
@@ -950,7 +951,7 @@
                 List<VolumeInfo> volumeInfos,
                 IAudioDeviceVolumeDispatcher callback,
                 boolean handlesVolumeAdjustment,
-                @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) {
+                @AudioDeviceVolumeManager.AbsoluteDeviceVolumeBehavior int behavior) {
             this.mParent = parent;
             this.mDevice = device;
             this.mVolumeInfos = volumeInfos;
@@ -8173,7 +8174,7 @@
             IAudioDeviceVolumeDispatcher cb, String packageName,
             AudioDeviceAttributes device, List<VolumeInfo> volumes,
             boolean handlesVolumeAdjustment,
-            @AudioManager.AbsoluteDeviceVolumeBehavior int deviceVolumeBehavior) {
+            @AudioDeviceVolumeManager.AbsoluteDeviceVolumeBehavior int deviceVolumeBehavior) {
         // verify permissions
         if (mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING)
                 != PackageManager.PERMISSION_GRANTED
@@ -8240,12 +8241,13 @@
     @android.annotation.EnforcePermission(anyOf = {
             MODIFY_AUDIO_ROUTING, MODIFY_AUDIO_SETTINGS_PRIVILEGED })
     public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
-            @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @Nullable String pkgName) {
+            @AudioDeviceVolumeManager.DeviceVolumeBehavior int deviceVolumeBehavior,
+            @Nullable String pkgName) {
         // verify permissions
         super.setDeviceVolumeBehavior_enforcePermission();
         // verify arguments
         Objects.requireNonNull(device);
-        AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
+        AudioDeviceVolumeManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
 
         device = retrieveBluetoothAddress(device);
 
@@ -8268,7 +8270,8 @@
     }
 
     private void setDeviceVolumeBehaviorInternal(@NonNull AudioDeviceAttributes device,
-            @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @NonNull String caller) {
+            @AudioDeviceVolumeManager.DeviceVolumeBehavior int deviceVolumeBehavior,
+            @NonNull String caller) {
         int audioSystemDeviceOut = device.getInternalType();
         boolean volumeBehaviorChanged = false;
         // update device masks based on volume behavior
@@ -8323,7 +8326,7 @@
     @android.annotation.EnforcePermission(anyOf = {
             MODIFY_AUDIO_ROUTING, QUERY_AUDIO_STATE,  MODIFY_AUDIO_SETTINGS_PRIVILEGED
     })
-    public @AudioManager.DeviceVolumeBehavior
+    public @AudioDeviceVolumeManager.DeviceVolumeBehavior
     int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
         // verify permissions
         super.getDeviceVolumeBehavior_enforcePermission();
@@ -8335,7 +8338,7 @@
         return getDeviceVolumeBehaviorInt(device);
     }
 
-    private @AudioManager.DeviceVolumeBehavior
+    private @AudioDeviceVolumeManager.DeviceVolumeBehavior
             int getDeviceVolumeBehaviorInt(@NonNull AudioDeviceAttributes device) {
         // Get the internal type set by the AudioDeviceAttributes constructor which is always more
         // exact (avoids double conversions) than a conversion from SDK type via
@@ -15354,7 +15357,8 @@
 
     /**
      * Returns whether the input device uses absolute volume behavior, including its variants.
-     * For included volume behaviors, see {@link AudioManager.AbsoluteDeviceVolumeBehavior}.
+     * For included volume behaviors, see
+     * {@link AudioDeviceVolumeManager.AbsoluteDeviceVolumeBehavior}.
      * <p>This is distinct from Bluetooth A2DP absolute volume behavior
      * ({@link #isA2dpAbsoluteVolumeDevice}).
      */
@@ -15381,7 +15385,7 @@
     }
 
     private void persistDeviceVolumeBehavior(int deviceType,
-            @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior) {
+            @AudioDeviceVolumeManager.DeviceVolumeBehavior int deviceVolumeBehavior) {
         if (DEBUG_VOL) {
             Log.d(TAG, "Persisting Volume Behavior for DeviceType: " + deviceType);
         }
@@ -15396,7 +15400,7 @@
         }
     }
 
-    @AudioManager.DeviceVolumeBehaviorState
+    @AudioDeviceVolumeManager.DeviceVolumeBehaviorState
     private int retrieveStoredDeviceVolumeBehavior(int deviceType) {
         return mSettings.getSystemIntForUser(mContentResolver,
                 getSettingsNameForDeviceVolumeBehavior(deviceType),
diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
index ab86433..62c3dbd 100644
--- a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
+++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceVolumeManager;
+import android.media.AudioManager;
 import android.media.VolumeInfo;
 
 import java.util.concurrent.Executor;
@@ -53,7 +54,7 @@
 
     /**
      * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior(
-     * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
+     * AudioDeviceAttributes, VolumeInfo, boolean, Executor, OnAudioDeviceVolumeChangedListener)}
      */
     void setDeviceAbsoluteVolumeBehavior(
             @NonNull AudioDeviceAttributes device,
@@ -64,7 +65,7 @@
 
     /**
      * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeAdjustOnlyBehavior(
-     * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
+     * AudioDeviceAttributes, VolumeInfo, boolean, Executor, OnAudioDeviceVolumeChangedListener)}
      */
     void setDeviceAbsoluteVolumeAdjustOnlyBehavior(
             @NonNull AudioDeviceAttributes device,
@@ -72,4 +73,16 @@
             boolean handlesVolumeAdjustment,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener);
+
+    /**
+     * Wraps {@link AudioDeviceVolumeManager#getDeviceVolumeBehavior(AudioDeviceAttributes)}
+     */
+    @AudioManager.DeviceVolumeBehavior
+    int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device);
+
+    /**
+     * Wraps {@link AudioDeviceVolumeManager#setDeviceVolumeBehavior(AudioDeviceAttributes, int)}
+     */
+    void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
+            @AudioDeviceVolumeManager.DeviceVolumeBehavior int deviceVolumeBehavior);
 }
diff --git a/services/core/java/com/android/server/hdmi/AudioManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioManagerWrapper.java
index fd4dd51..6d01e2d 100644
--- a/services/core/java/com/android/server/hdmi/AudioManagerWrapper.java
+++ b/services/core/java/com/android/server/hdmi/AudioManagerWrapper.java
@@ -85,18 +85,6 @@
     void setWiredDeviceConnectionState(int device, int state, String address, String name);
 
     /**
-     * Wraps {@link AudioManager#getDeviceVolumeBehavior(AudioDeviceAttributes)}
-     */
-    @AudioManager.DeviceVolumeBehavior
-    int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device);
-
-    /**
-     * Wraps {@link AudioManager#setDeviceVolumeBehavior(AudioDeviceAttributes, int)}
-     */
-    void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
-            @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior);
-
-    /**
      * Wraps {@link AudioManager#getDevicesForAttributes(AudioAttributes)}
      */
     @NonNull
diff --git a/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java
index 10cbb00..02d8579 100644
--- a/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java
+++ b/services/core/java/com/android/server/hdmi/DefaultAudioDeviceVolumeManagerWrapper.java
@@ -16,11 +16,14 @@
 
 package com.android.server.hdmi;
 
+import static android.media.audio.Flags.unifyAbsoluteVolumeManagement;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.content.Context;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceVolumeManager;
+import android.media.AudioManager;
 import android.media.VolumeInfo;
 
 import java.util.concurrent.Executor;
@@ -38,9 +41,11 @@
     private static final String TAG = "AudioDeviceVolumeManagerWrapper";
 
     private final AudioDeviceVolumeManager mAudioDeviceVolumeManager;
+    private final AudioManager mAudioManager;
 
     public DefaultAudioDeviceVolumeManagerWrapper(Context context) {
         mAudioDeviceVolumeManager = new AudioDeviceVolumeManager(context);
+        mAudioManager = context.getSystemService(AudioManager.class);
     }
 
     @Override
@@ -78,4 +83,24 @@
         mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeAdjustOnlyBehavior(device, volume,
                 handlesVolumeAdjustment, executor, vclistener);
     }
+
+    @Override
+    @AudioDeviceVolumeManager.DeviceVolumeBehavior
+    public int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
+        if (!unifyAbsoluteVolumeManagement()) {
+            int deviceBehavior = mAudioManager.getDeviceVolumeBehavior(device);
+            return deviceBehavior;
+        }
+        return mAudioDeviceVolumeManager.getDeviceVolumeBehavior(device);
+    }
+
+    @Override
+    public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
+            @AudioDeviceVolumeManager.DeviceVolumeBehavior int deviceVolumeBehavior) {
+        if (!unifyAbsoluteVolumeManagement()) {
+            int deviceBehavior = deviceVolumeBehavior;
+            mAudioManager.setDeviceVolumeBehavior(device, deviceBehavior);
+        }
+        mAudioDeviceVolumeManager.setDeviceVolumeBehavior(device, deviceVolumeBehavior);
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/DefaultAudioManagerWrapper.java b/services/core/java/com/android/server/hdmi/DefaultAudioManagerWrapper.java
index 061e145..6627154 100644
--- a/services/core/java/com/android/server/hdmi/DefaultAudioManagerWrapper.java
+++ b/services/core/java/com/android/server/hdmi/DefaultAudioManagerWrapper.java
@@ -94,18 +94,6 @@
     }
 
     @Override
-    @AudioManager.DeviceVolumeBehavior
-    public int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
-        return mAudioManager.getDeviceVolumeBehavior(device);
-    }
-
-    @Override
-    public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
-            @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior) {
-        mAudioManager.setDeviceVolumeBehavior(device, deviceVolumeBehavior);
-    }
-
-    @Override
     @NonNull
     public List<AudioDeviceAttributes> getDevicesForAttributes(
             @NonNull AudioAttributes attributes) {
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java
index 9118c46..574e484 100644
--- a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromTv.java
@@ -167,7 +167,11 @@
     private boolean handleReportPowerStatus(int powerStatus) {
         switch (powerStatus) {
             case HdmiControlManager.POWER_STATUS_ON:
-                selectDevice();
+                if (tv().getActiveSource().physicalAddress == mTarget.getPhysicalAddress()) {
+                    finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+                } else {
+                    selectDevice();
+                }
                 return true;
             case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
                 if (mPowerStatusCounter < 4) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index fdd0ef2..41b0b4d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4595,7 +4595,7 @@
      * Wrapper for {@link AudioManager#getDeviceVolumeBehavior} that takes advantage of cached
      * results for the volume behaviors of HDMI audio devices.
      */
-    @AudioManager.DeviceVolumeBehavior
+    @AudioDeviceVolumeManager.DeviceVolumeBehavior
     private int getDeviceVolumeBehavior(AudioDeviceAttributes device) {
         if (AVB_AUDIO_OUTPUT_DEVICES.contains(device)) {
             synchronized (mLock) {
@@ -4604,7 +4604,7 @@
                 }
             }
         }
-        return getAudioManager().getDeviceVolumeBehavior(device);
+        return getAudioDeviceVolumeManager().getDeviceVolumeBehavior(device);
     }
 
     /**
@@ -4695,7 +4695,7 @@
         // Condition 3: All AVB-capable audio outputs already use full/absolute volume behavior
         // We only need to check the first AVB-capable audio output because only TV panels
         // have more than one of them, and they always have the same volume behavior.
-        @AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
+        @AudioDeviceVolumeManager.DeviceVolumeBehavior int currentVolumeBehavior =
                 getDeviceVolumeBehavior(getAvbCapableAudioOutputDevices().get(0));
         boolean alreadyUsingFullOrAbsoluteVolume =
                 FULL_AND_ABSOLUTE_VOLUME_BEHAVIORS.contains(currentVolumeBehavior);
@@ -4719,7 +4719,8 @@
         // Condition 5: The System Audio device supports <Set Audio Volume Level>
         switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
             case DeviceFeatures.FEATURE_SUPPORTED:
-                if (currentVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
+                if (currentVolumeBehavior
+                        != AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
                     // Start an action that will call enableAbsoluteVolumeBehavior
                     // once the System Audio device sends <Report Audio Status>
                     localCecDevice.startNewAvbAudioStatusAction(
@@ -4731,13 +4732,15 @@
                 // This allows the device to display numeric volume UI for the System Audio device.
                 if (tv() != null && mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled) {
                     if (currentVolumeBehavior
-                            != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY) {
+                            != AudioDeviceVolumeManager
+                            .DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY) {
                         // If we're currently using absolute volume behavior, switch to full volume
                         // behavior until we successfully adopt adjust-only absolute volume behavior
-                        if (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
+                        if (currentVolumeBehavior
+                                == AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
                             for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
-                                getAudioManager().setDeviceVolumeBehavior(device,
-                                        AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+                                getAudioDeviceVolumeManager().setDeviceVolumeBehavior(device,
+                                        AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
                             }
                         }
                         // Start an action that will call enableAbsoluteVolumeBehavior
@@ -4750,7 +4753,8 @@
                 }
                 return;
             case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN:
-                if (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
+                if (currentVolumeBehavior
+                        == AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
                     switchToFullVolumeBehavior();
                 }
                 localCecDevice.querySetAudioVolumeLevelSupport(
@@ -4773,8 +4777,8 @@
 
         for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
             if (ABSOLUTE_VOLUME_BEHAVIORS.contains(getDeviceVolumeBehavior(device))) {
-                getAudioManager().setDeviceVolumeBehavior(device,
-                        AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+                getAudioDeviceVolumeManager().setDeviceVolumeBehavior(device,
+                        AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
             }
         }
     }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index 6db62c8..ccb9e3e 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -301,7 +301,8 @@
             throw new IllegalArgumentException(
                     "Unknown session ID in closeSession: id=" + sessionId);
         }
-        halCloseEndpointSession(sessionId, ContextHubServiceUtil.toHalReason(reason));
+        mEndpointManager.halCloseEndpointSession(
+                sessionId, ContextHubServiceUtil.toHalReason(reason));
     }
 
     @Override
@@ -312,7 +313,7 @@
             // Iterate in reverse since cleanupSessionResources will remove the entry
             for (int i = mSessionMap.size() - 1; i >= 0; i--) {
                 int id = mSessionMap.keyAt(i);
-                halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE);
+                mEndpointManager.halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE);
                 cleanupSessionResources(id);
             }
         }
@@ -444,7 +445,8 @@
                     int id = mSessionMap.keyAt(i);
                     HubEndpointInfo target = mSessionMap.get(id).getRemoteEndpointInfo();
                     if (!hasEndpointPermissions(target)) {
-                        halCloseEndpointSessionNoThrow(id, Reason.PERMISSION_DENIED);
+                        mEndpointManager.halCloseEndpointSessionNoThrow(
+                                id, Reason.PERMISSION_DENIED);
                         onCloseEndpointSession(id, Reason.PERMISSION_DENIED);
                         // Resource cleanup is done in onCloseEndpointSession
                     }
@@ -503,17 +505,7 @@
         mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */);
     }
 
-    /* package */ void onEndpointSessionOpenRequest(
-            int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
-        Optional<Byte> error =
-                onEndpointSessionOpenRequestInternal(sessionId, initiator, serviceDescriptor);
-        if (error.isPresent()) {
-            halCloseEndpointSessionNoThrow(sessionId, error.get());
-            onCloseEndpointSession(sessionId, error.get());
-            // Resource cleanup is done in onCloseEndpointSession
-        }
-    }
-
+    /** Handle close endpoint callback to the client side */
     /* package */ void onCloseEndpointSession(int sessionId, byte reason) {
         if (!cleanupSessionResources(sessionId)) {
             Log.w(TAG, "Unknown session ID in onCloseEndpointSession: id=" + sessionId);
@@ -585,7 +577,7 @@
         }
     }
 
-    private Optional<Byte> onEndpointSessionOpenRequestInternal(
+    /* package */ Optional<Byte> onEndpointSessionOpenRequest(
             int sessionId, HubEndpointInfo initiator, String serviceDescriptor) {
         if (!hasEndpointPermissions(initiator)) {
             Log.e(
@@ -594,15 +586,41 @@
                             + initiator
                             + " doesn't have permission for "
                             + mEndpointInfo);
-            return Optional.of(Reason.PERMISSION_DENIED);
+            byte reason = Reason.PERMISSION_DENIED;
+            onCloseEndpointSession(sessionId, reason);
+            return Optional.of(reason);
         }
 
+        // Check & handle error cases for duplicated session id.
+        final boolean existingSession;
+        final boolean existingSessionActive;
         synchronized (mOpenSessionLock) {
             if (hasSessionId(sessionId)) {
-                Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId);
-                return Optional.of(Reason.UNSPECIFIED);
+                existingSession = true;
+                existingSessionActive = mSessionMap.get(sessionId).isActive();
+                Log.w(
+                        TAG,
+                        "onEndpointSessionOpenRequest: "
+                                + "Existing session ID: "
+                                + sessionId
+                                + ", isActive: "
+                                + existingSessionActive);
+            } else {
+                existingSession = false;
+                existingSessionActive = false;
+                mSessionMap.put(sessionId, new Session(initiator, true));
             }
-            mSessionMap.put(sessionId, new Session(initiator, true));
+        }
+
+        if (existingSession) {
+            if (existingSessionActive) {
+                // Existing session is already active, call onSessionOpenComplete.
+                openSessionRequestComplete(sessionId);
+                return Optional.empty();
+            }
+            // Reject the session open request for now. Consider invalidating previous pending
+            // session open request based on timeout.
+            return Optional.of(Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
         }
 
         boolean success =
@@ -610,7 +628,11 @@
                         (consumer) ->
                                 consumer.onSessionOpenRequest(
                                         sessionId, initiator, serviceDescriptor));
-        return success ? Optional.empty() : Optional.of(Reason.UNSPECIFIED);
+        byte reason = Reason.UNSPECIFIED;
+        if (!success) {
+            onCloseEndpointSession(sessionId, reason);
+        }
+        return success ? Optional.empty() : Optional.of(reason);
     }
 
     private byte onMessageReceivedInternal(int sessionId, HubMessage message) {
@@ -657,29 +679,6 @@
     }
 
     /**
-     * Calls the HAL closeEndpointSession API.
-     *
-     * @param sessionId The session ID to close
-     * @param halReason The HAL reason
-     */
-    private void halCloseEndpointSession(int sessionId, byte halReason) throws RemoteException {
-        try {
-            mHubInterface.closeEndpointSession(sessionId, halReason);
-        } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
-            throw e;
-        }
-    }
-
-    /** Same as halCloseEndpointSession but does not throw the exception */
-    private void halCloseEndpointSessionNoThrow(int sessionId, byte halReason) {
-        try {
-            halCloseEndpointSession(sessionId, halReason);
-        } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
-            Log.e(TAG, "Exception while calling HAL closeEndpointSession", e);
-        }
-    }
-
-    /**
      * Cleans up resources related to a session with the provided ID.
      *
      * @param sessionId The session ID to clean up resources for
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
index 8ab581e..e156159 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
@@ -29,6 +29,7 @@
 import android.hardware.contexthub.IContextHubEndpointCallback;
 import android.hardware.contexthub.IEndpointCommunication;
 import android.hardware.contexthub.MessageDeliveryStatus;
+import android.hardware.contexthub.Reason;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Log;
@@ -42,6 +43,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
@@ -316,6 +318,11 @@
         }
     }
 
+    /** Returns if a sessionId can be allocated for the service hub. */
+    private boolean isSessionIdAllocatedForService(int sessionId) {
+        return sessionId > mMaxSessionId || sessionId < mMinSessionId;
+    }
+
     /**
      * Unregisters an endpoint given its ID.
      *
@@ -337,8 +344,7 @@
         }
     }
 
-    @Override
-    public void onEndpointSessionOpenRequest(
+    private Optional<Byte> onEndpointSessionOpenRequestInternal(
             int sessionId,
             HubEndpointInfo.HubEndpointIdentifier destination,
             HubEndpointInfo.HubEndpointIdentifier initiator,
@@ -348,7 +354,7 @@
                     TAG,
                     "onEndpointSessionOpenRequest: invalid destination hub ID: "
                             + destination.getHub());
-            return;
+            return Optional.of(Reason.ENDPOINT_INVALID);
         }
         ContextHubEndpointBroker broker = mEndpointMap.get(destination.getEndpoint());
         if (broker == null) {
@@ -356,7 +362,7 @@
                     TAG,
                     "onEndpointSessionOpenRequest: unknown destination endpoint ID: "
                             + destination.getEndpoint());
-            return;
+            return Optional.of(Reason.ENDPOINT_INVALID);
         }
         HubEndpointInfo initiatorInfo = mHubInfoRegistry.getEndpointInfo(initiator);
         if (initiatorInfo == null) {
@@ -364,9 +370,29 @@
                     TAG,
                     "onEndpointSessionOpenRequest: unknown initiator endpoint ID: "
                             + initiator.getEndpoint());
-            return;
+            return Optional.of(Reason.ENDPOINT_INVALID);
         }
-        broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor);
+        if (!isSessionIdAllocatedForService(sessionId)) {
+            Log.e(
+                    TAG,
+                    "onEndpointSessionOpenRequest: invalid session ID, rejected:"
+                            + " sessionId="
+                            + sessionId);
+            return Optional.of(Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
+        }
+        return broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor);
+    }
+
+    @Override
+    public void onEndpointSessionOpenRequest(
+            int sessionId,
+            HubEndpointInfo.HubEndpointIdentifier destination,
+            HubEndpointInfo.HubEndpointIdentifier initiator,
+            String serviceDescriptor) {
+        Optional<Byte> errorOptional =
+                onEndpointSessionOpenRequestInternal(
+                        sessionId, destination, initiator, serviceDescriptor);
+        errorOptional.ifPresent((error) -> halCloseEndpointSessionNoThrow(sessionId, error));
     }
 
     @Override
@@ -418,6 +444,30 @@
         }
     }
 
+    /**
+     * Calls the HAL closeEndpointSession API.
+     *
+     * @param sessionId The session ID to close
+     * @param halReason The HAL reason
+     */
+    /* package */ void halCloseEndpointSession(int sessionId, byte halReason)
+            throws RemoteException {
+        try {
+            mHubInterface.closeEndpointSession(sessionId, halReason);
+        } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+            throw e;
+        }
+    }
+
+    /** Same as halCloseEndpointSession but does not throw the exception */
+    /* package */ void halCloseEndpointSessionNoThrow(int sessionId, byte halReason) {
+        try {
+            halCloseEndpointSession(sessionId, halReason);
+        } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
+            Log.e(TAG, "Exception while calling HAL closeEndpointSession", e);
+        }
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index 7b4c563..7fd400e 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -518,7 +518,6 @@
         if (!Flags.gnssAssistanceInterfaceJni()) {
             return;
         }
-        Preconditions.checkState(!mRegistered);
         Preconditions.checkState(mGnssAssistanceCallbacks == null);
         mGnssAssistanceCallbacks = Objects.requireNonNull(callbacks);
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 8948bd1..78554bd 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6173,10 +6173,15 @@
         }
 
         @Override
-        public Map<String, AutomaticZenRule> getAutomaticZenRules() {
+        public ParceledListSlice getAutomaticZenRules() {
             int callingUid = Binder.getCallingUid();
             enforcePolicyAccess(callingUid, "getAutomaticZenRules");
-            return mZenModeHelper.getAutomaticZenRules(getCallingZenUser(), callingUid);
+            List<AutomaticZenRule.AzrWithId> ruleList = new ArrayList<>();
+            for (Map.Entry<String, AutomaticZenRule> rule : mZenModeHelper.getAutomaticZenRules(
+                    getCallingZenUser(), callingUid).entrySet()) {
+                ruleList.add(new AutomaticZenRule.AzrWithId(rule.getKey(), rule.getValue()));
+            }
+            return new ParceledListSlice<>(ruleList);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 233b577..33a7e74 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -7724,6 +7724,7 @@
 
         pw.print("    Has profile owner: ");
         pw.println(mIsUserManaged.get(userId));
+
         pw.println("    Restrictions:");
         synchronized (mRestrictionsLock) {
             UserRestrictionsUtils.dumpRestrictions(
@@ -7756,6 +7757,9 @@
             }
         }
 
+        pw.print("    Can have profile: ");
+        pw.println(userInfo.canHaveProfile());
+
         if (userData.userProperties != null) {
             userData.userProperties.println(pw, "    ");
         }
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
index 6872ca9..8c3b7c6 100644
--- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
@@ -17,11 +17,13 @@
 package com.android.server.security.advancedprotection;
 
 import static android.provider.Settings.Secure.ADVANCED_PROTECTION_MODE;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 
 import android.Manifest;
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.StatsManager;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.Binder;
@@ -45,6 +47,7 @@
 import android.security.advancedprotection.AdvancedProtectionProtoEnums;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.StatsEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
@@ -59,6 +62,7 @@
 import com.android.server.security.advancedprotection.features.DisallowInstallUnknownSourcesAdvancedProtectionHook;
 import com.android.server.security.advancedprotection.features.MemoryTaggingExtensionHook;
 import com.android.server.security.advancedprotection.features.UsbDataAdvancedProtectionHook;
+import com.android.server.security.advancedprotection.features.DisallowWepAdvancedProtectionProvider;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -131,7 +135,18 @@
           } catch (Exception e) {
             Slog.e(TAG, "Failed to initialize UsbDataAdvancedProtection", e);
           }
-      }
+        }
+
+        mProviders.add(new DisallowWepAdvancedProtectionProvider());
+    }
+
+    private void initLogging() {
+        StatsManager statsManager = mContext.getSystemService(StatsManager.class);
+        statsManager.setPullAtomCallback(
+                FrameworkStatsLog.ADVANCED_PROTECTION_STATE_INFO,
+                null, // use default PullAtomMetadata values
+                DIRECT_EXECUTOR,
+                new AdvancedProtectionStatePullAtomCallback());
     }
 
     // Only for tests
@@ -303,7 +318,7 @@
         getAdvancedProtectionFeatures_enforcePermission();
         List<AdvancedProtectionFeature> features = new ArrayList<>();
         for (int i = 0; i < mProviders.size(); i++) {
-            features.addAll(mProviders.get(i).getFeatures());
+            features.addAll(mProviders.get(i).getFeatures(mContext));
         }
 
         for (int i = 0; i < mHooks.size(); i++) {
@@ -341,7 +356,7 @@
         writer.println("  Providers: ");
         mProviders.stream().forEach(provider -> {
             writer.println("    " + provider.getClass().getSimpleName());
-            provider.getFeatures().stream().forEach(feature -> {
+            provider.getFeatures(mContext).stream().forEach(feature -> {
                 writer.println("      " + feature.getClass().getSimpleName());
             });
         });
@@ -396,6 +411,7 @@
                     Slog.i(TAG, "Advanced protection is enabled");
                 }
                 mService.initFeatures(enabled);
+                mService.initLogging();
             }
         }
     }
@@ -497,4 +513,22 @@
             }
         }
     }
+
+    private class AdvancedProtectionStatePullAtomCallback
+            implements StatsManager.StatsPullAtomCallback {
+
+        @Override
+        public int onPullAtom(int atomTag, List<StatsEvent> data) {
+            if (atomTag != FrameworkStatsLog.ADVANCED_PROTECTION_STATE_INFO) {
+                return StatsManager.PULL_SKIP;
+            }
+
+            data.add(
+                    FrameworkStatsLog.buildStatsEvent(
+                            FrameworkStatsLog.ADVANCED_PROTECTION_STATE_INFO,
+                            /*enabled*/ isAdvancedProtectionEnabledInternal(),
+                            /*hours_since_enabled*/ hoursSinceLastChange()));
+            return StatsManager.PULL_SUCCESS;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java
index ed451f1..6498cfc 100644
--- a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java
+++ b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.server.security.advancedprotection.features;
 
+import android.annotation.NonNull;
+import android.content.Context;
 import android.security.advancedprotection.AdvancedProtectionFeature;
 
 import java.util.List;
@@ -23,5 +25,5 @@
 /** @hide */
 public abstract class AdvancedProtectionProvider {
     /** The list of features provided */
-    public abstract List<AdvancedProtectionFeature> getFeatures();
+    public abstract List<AdvancedProtectionFeature> getFeatures(@NonNull Context context);
 }
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java
new file mode 100644
index 0000000..1505f68
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowWepAdvancedProtectionProvider.java
@@ -0,0 +1,31 @@
+/*
+ * 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.server.security.advancedprotection.features;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+
+import java.util.List;
+
+public class DisallowWepAdvancedProtectionProvider extends AdvancedProtectionProvider {
+    public List<AdvancedProtectionFeature> getFeatures(@NonNull Context context) {
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        return wifiManager.getAvailableAdvancedProtectionFeatures();
+    }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
index f413fe3..58f34d0 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
@@ -36,7 +36,4 @@
 
     /** Notifies when the screen starts turning on and is not yet visible to the user. */
     public abstract void onScreenTurningOn(int displayId);
-
-    /** Notifies when the keyguard is going away. Sent right after the bouncer is gone. */
-    public abstract void onKeyguardGoingAway();
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index e7da33d..274175a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -22,6 +22,7 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.Flags.fixWallpaperChanged;
 import static android.app.Flags.liveWallpaperContentHandling;
+import static android.app.Flags.notifyKeyguardEvents;
 import static android.app.Flags.removeNextWallpaperComponent;
 import static android.app.WallpaperManager.COMMAND_REAPPLY;
 import static android.app.WallpaperManager.FLAG_LOCK;
@@ -1709,8 +1710,32 @@
         mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper,
                 mWallpaperCropper);
         LocalServices.addService(WallpaperManagerInternal.class, new LocalService());
+
+        LocalServices.getService(ActivityTaskManagerInternal.class)
+                .registerScreenObserver(mKeyguardObserver);
+
     }
 
+    private final ActivityTaskManagerInternal.ScreenObserver mKeyguardObserver =
+            new ActivityTaskManagerInternal.ScreenObserver() {
+                @Override
+                public void onKeyguardStateChanged(boolean isShowing) {
+                    if (!notifyKeyguardEvents()) {
+                        return;
+                    }
+                    if (isShowing) {
+                        notifyKeyguardAppearing();
+                    } else {
+                        notifyKeyguardGoingAway();
+                    }
+                }
+
+                @Override
+                public void onKeyguardGoingAway() {
+                    notifyKeyguardGoingAway();
+                }
+            };
+
     private final class LocalService extends WallpaperManagerInternal {
         @Override
         public void onDisplayAddSystemDecorations(int displayId) {
@@ -1733,11 +1758,6 @@
         public void onScreenTurningOn(int displayId) {
             notifyScreenTurningOn(displayId);
         }
-
-        @Override
-        public void onKeyguardGoingAway() {
-            notifyKeyguardGoingAway();
-        }
     }
 
     void initialize() {
@@ -2571,6 +2591,18 @@
         return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
     }
 
+    private boolean hasPermission(WallpaperData data, String permission) {
+        try {
+            return PackageManager.PERMISSION_GRANTED == mIPackageManager.checkPermission(
+                    permission,
+                    data.getComponent().getPackageName(),
+                    data.userId);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to check wallpaper service permission", e);
+            return false;
+        }
+    }
+
     private boolean hasAppOpPermission(String permission, int callingUid, String callingPackage,
             String attributionTag, String message) {
         final String op = AppOpsManager.permissionToOp(permission);
@@ -2873,16 +2905,37 @@
      * Propagate a keyguard going away event to the wallpaper engine.
      */
     private void notifyKeyguardGoingAway() {
+        dispatchKeyguardCommand(WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY);
+    }
+
+    /**
+     * Propagate a keyguard appearing event to the wallpaper engine.
+     */
+    private void notifyKeyguardAppearing() {
+        dispatchKeyguardCommand(WallpaperManager.COMMAND_KEYGUARD_APPEARING);
+    }
+
+    /**
+     * Propagate a keyguard-related event to the wallpaper engine.
+     *
+     * When the flag below is enabled, the event will only be dispatched in case the recipient
+     * has {@link android.Manifest.pertmission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE} permission.
+     */
+    private void dispatchKeyguardCommand(String command) {
         synchronized (mLock) {
             for (WallpaperData data : getActiveWallpapers()) {
+                if (notifyKeyguardEvents() && !hasPermission(
+                        data, android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)) {
+                    continue;
+                }
+
                 data.connection.forEachDisplayConnector(displayConnector -> {
                     if (displayConnector.mEngine != null) {
                         try {
                             displayConnector.mEngine.dispatchWallpaperCommand(
-                                    WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY,
-                                    -1, -1, -1, new Bundle());
+                                    command, -1, -1, -1, new Bundle());
                         } catch (RemoteException e) {
-                            Slog.w(TAG, "Failed to notify that the keyguard is going away", e);
+                            Slog.w(TAG, "Failed to dispatch wallpaper command: " + command, e);
                         }
                     }
                 });
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index a731bf7..70fc6ba 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -82,6 +82,7 @@
      */
     @VisibleForTesting
     static final int SNAPSHOT_MODE_NONE = 2;
+    static final float THEME_SNAPSHOT_MIN_Length = 128.0f;
 
     protected final WindowManagerService mService;
     protected final float mHighResSnapshotScale;
@@ -436,14 +437,21 @@
         final Rect taskBounds = source.getBounds();
         final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
         final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
+        final int taskWidth = taskBounds.width();
+        final int taskHeight = taskBounds.height();
+        float scale = mHighResSnapshotScale;
+        if (Flags.reduceTaskSnapshotMemoryUsage()) {
+            final int minLength = Math.min(taskWidth, taskHeight);
+            if (THEME_SNAPSHOT_MIN_Length < minLength) {
+                scale = Math.min(THEME_SNAPSHOT_MIN_Length / minLength, scale);
+            }
+        }
         final SnapshotDrawerUtils.SystemBarBackgroundPainter
                 decorPainter = new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags,
                 attrs.privateFlags, attrs.insetsFlags.appearance, taskDescription,
-                mHighResSnapshotScale, mainWindow.getRequestedVisibleTypes());
-        final int taskWidth = taskBounds.width();
-        final int taskHeight = taskBounds.height();
-        final int width = (int) (taskWidth * mHighResSnapshotScale);
-        final int height = (int) (taskHeight * mHighResSnapshotScale);
+                scale, mainWindow.getRequestedVisibleTypes());
+        final int width = (int) (taskWidth * scale);
+        final int height = (int) (taskHeight * scale);
         final RenderNode node = RenderNode.create("SnapshotController", null);
         node.setLeftTopRightBottom(0, 0, width, height);
         node.setClipToBounds(false);
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 5cc186c..a19f438 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -272,6 +272,12 @@
             mActivityOptions = interceptResult.getActivityOptions();
             mCallingPid = mRealCallingPid;
             mCallingUid = mRealCallingUid;
+            // When an activity launch is intercepted, Intent#prepareToLeaveProcess is not called
+            // since the interception happens in the system_server. So if any activity is calling
+            // a trampoline activity, the keys do not get collected. Since all the interceptors
+            // are present in the system_server, add the creator token before launching the
+            // intercepted intent.
+            mService.mAmInternal.addCreatorToken(mIntent, mCallingPackage);
             if (interceptResult.isActivityResolved()) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index c243cdc..21b730e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -124,8 +124,9 @@
     public static final String ASSIST_KEY_RECEIVER_EXTRAS = "receiverExtras";
 
     public interface ScreenObserver {
-        void onAwakeStateChanged(boolean isAwake);
-        void onKeyguardStateChanged(boolean isShowing);
+        default void onAwakeStateChanged(boolean isAwake) {}
+        default void onKeyguardStateChanged(boolean isShowing) {}
+        default void onKeyguardGoingAway() {}
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 9c96566..46d24b0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -281,7 +281,6 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriGrantsManagerInternal;
-import com.android.server.wallpaper.WallpaperManagerInternal;
 import com.android.server.wm.utils.WindowStyleCache;
 import com.android.wm.shell.Flags;
 
@@ -373,7 +372,6 @@
     private ComponentName mSysUiServiceComponent;
     private PermissionPolicyInternal mPermissionPolicyInternal;
     private StatusBarManagerInternal mStatusBarManagerInternal;
-    private WallpaperManagerInternal mWallpaperManagerInternal;
     private UserManagerInternal mUserManagerInternal;
     @VisibleForTesting
     final ActivityTaskManagerInternal mInternal;
@@ -3719,10 +3717,12 @@
                 if (isPowerModePreApplied && !foundResumed) {
                     endPowerMode(POWER_MODE_REASON_START_ACTIVITY);
                 }
-            }
-            WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
-            if (wallpaperManagerInternal != null) {
-                wallpaperManagerInternal.onKeyguardGoingAway();
+
+                mH.post(() -> {
+                    for (int i = mScreenObservers.size() - 1; i >= 0; i--) {
+                        mScreenObservers.get(i).onKeyguardGoingAway();
+                    }
+                });
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -5573,13 +5573,6 @@
         return mStatusBarManagerInternal;
     }
 
-    WallpaperManagerInternal getWallpaperManagerInternal() {
-        if (mWallpaperManagerInternal == null) {
-            mWallpaperManagerInternal = LocalServices.getService(WallpaperManagerInternal.class);
-        }
-        return mWallpaperManagerInternal;
-    }
-
     UserManagerInternal getUserManagerInternal() {
         if (mUserManagerInternal == null) {
             mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
diff --git a/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java
index 9596093..cd6a014 100644
--- a/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java
@@ -156,6 +156,8 @@
     void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
         if (mNeedsSafeRegionBounds) {
             pw.println(prefix + " mNeedsSafeRegionBounds=true");
+            pw.println(
+                    prefix + " latestSafeRegionBoundsOnActivity=" + getLatestSafeRegionBounds());
         }
         if (isLetterboxedForSafeRegionOnlyAllowed()) {
             pw.println(prefix + " isLetterboxForSafeRegionOnlyAllowed=true");
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index fee5566..d808726 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -69,11 +69,11 @@
      * Calculates the final aspect ratio of an launching activity based on the task it will be
      * launched in. Takes into account any min or max aspect ratio constraints.
      */
-    float calculateAspectRatio(@NonNull Task task) {
+    float calculateAspectRatio(@NonNull Task task, boolean hasOrientationMismatch) {
         final float maxAspectRatio = getMaxAspectRatio();
         final float minAspectRatio = getMinAspectRatio(task);
         float desiredAspectRatio = 0;
-        desiredAspectRatio = getDesiredAspectRatio(task);
+        desiredAspectRatio = getDesiredAspectRatio(task, hasOrientationMismatch);
         if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
             desiredAspectRatio = maxAspectRatio;
         } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
@@ -87,13 +87,14 @@
      * any min or max aspect ratio constraints.
      */
     @VisibleForTesting
-    float getDesiredAspectRatio(@NonNull Task task) {
+    float getDesiredAspectRatio(@NonNull Task task, boolean hasOrientationMismatch) {
         final float letterboxAspectRatioOverride = getFixedOrientationLetterboxAspectRatio(task);
         // Aspect ratio as suggested by the system. Apps requested mix/max aspect ratio will
         // be respected in #calculateAspectRatio.
         if (isDefaultMultiWindowLetterboxAspectRatioDesired(task)) {
             return DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
-        } else if (letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
+        } else if (hasOrientationMismatch
+                && letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
             return letterboxAspectRatioOverride;
         }
         return AppCompatUtils.computeAspectRatio(task.getDisplayArea().getBounds());
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index d935432..83ca5f6 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.isFixedOrientation;
 import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
 import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
@@ -32,8 +31,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
-import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.content.pm.ActivityInfo.WindowLayout;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.util.Size;
@@ -152,19 +151,25 @@
         }
         final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
                 activity.mAppCompatController.getDesktopAspectRatioPolicy();
-        float appAspectRatio = desktopAppCompatAspectRatioPolicy.calculateAspectRatio(task);
+        final int stableBoundsOrientation = stableBounds.height() >= stableBounds.width()
+                ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+        int activityOrientation = getActivityConfigurationOrientation(
+                activity, task, stableBoundsOrientation);
+        // Use orientation mismatch to resolve aspect ratio to match fixed orientation letterboxing
+        // policy in {@link ActivityRecord.resolveFixedOrientationConfiguration}
+        final boolean hasOrientationMismatch = stableBoundsOrientation != activityOrientation;
+        float appAspectRatio = desktopAppCompatAspectRatioPolicy.calculateAspectRatio(
+                task, hasOrientationMismatch);
         final float tdaWidth = stableBounds.width();
         final float tdaHeight = stableBounds.height();
-        final int taskConfigOrientation = task.getConfiguration().orientation;
-        final int activityOrientation = getActivityOrientation(activity, task);
-        final Size initialSize = switch (taskConfigOrientation) {
+        final Size initialSize = switch (stableBoundsOrientation) {
             case ORIENTATION_LANDSCAPE -> {
                 // Device in landscape orientation.
                 if (appAspectRatio == 0) {
                     appAspectRatio = 1;
                 }
                 if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, task)) {
-                    if (isFixedOrientationPortrait(activityOrientation)) {
+                    if (hasOrientationMismatch) {
                         // For portrait resizeable activities, respect apps fullscreen width but
                         // apply ideal size height.
                         yield new Size((int) ((tdaHeight / appAspectRatio) + 0.5f),
@@ -183,7 +188,7 @@
                 final int customPortraitWidthForLandscapeApp = screenBounds.width()
                         - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
                 if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, task)) {
-                    if (isFixedOrientationLandscape(activityOrientation)) {
+                    if (hasOrientationMismatch) {
                         if (appAspectRatio == 0) {
                             appAspectRatio = tdaWidth / (tdaWidth - 1);
                         }
@@ -198,7 +203,7 @@
                 if (appAspectRatio == 0) {
                     appAspectRatio = 1;
                 }
-                if (isFixedOrientationLandscape(activityOrientation)) {
+                if (hasOrientationMismatch) {
                     // For landscape unresizeable activities, apply custom app width to ideal size
                     // and calculate maximum size with this area while maintaining original aspect
                     // ratio.
@@ -230,19 +235,23 @@
                 && !desktopAppCompatAspectRatioPolicy.hasMinAspectRatioOverride(task);
     }
 
-    private static @ScreenOrientation int getActivityOrientation(
-            @NonNull ActivityRecord activity, @NonNull Task task) {
+    private static @Configuration.Orientation int getActivityConfigurationOrientation(
+            @NonNull ActivityRecord activity, @NonNull Task task,
+            @Configuration.Orientation int stableBoundsOrientation) {
         final int activityOrientation = activity.getOverrideOrientation();
         final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
                 activity.mAppCompatController.getDesktopAspectRatioPolicy();
-        if (desktopAppCompatAspectRatioPolicy.shouldApplyUserMinAspectRatioOverride(task)
+        if ((desktopAppCompatAspectRatioPolicy.shouldApplyUserMinAspectRatioOverride(task)
                 && (!isFixedOrientation(activityOrientation)
-                    || activityOrientation == SCREEN_ORIENTATION_LOCKED)) {
+                    || activityOrientation == SCREEN_ORIENTATION_LOCKED))
+                || isFixedOrientationPortrait(activityOrientation)) {
             // If a user aspect ratio override should be applied, treat the activity as portrait if
             // it has not specified a fix orientation.
-            return SCREEN_ORIENTATION_PORTRAIT;
+            return ORIENTATION_PORTRAIT;
         }
-        return activityOrientation;
+        // If activity orientation is undefined inherit task orientation.
+        return isFixedOrientationLandscape(activityOrientation)
+                ?  ORIENTATION_LANDSCAPE : stableBoundsOrientation;
     }
 
     /**
@@ -252,7 +261,7 @@
     // TODO(b/400617906): Merge duplicate initial bounds calculations to shared class.
     @NonNull
     private static Size maximizeSizeGivenAspectRatio(
-            @ScreenOrientation int orientation,
+            @Configuration.Orientation int orientation,
             @NonNull Size targetArea,
             float aspectRatio,
             int captionHeight
@@ -261,7 +270,7 @@
         final int targetWidth = targetArea.getWidth();
         final int finalHeight;
         final int finalWidth;
-        if (isFixedOrientationPortrait(orientation)) {
+        if (orientation == ORIENTATION_PORTRAIT) {
             // Portrait activity.
             // Calculate required width given ideal height and aspect ratio.
             int tempWidth = (int) (targetHeight / aspectRatio);
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 2798e84..ab87459 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -218,6 +218,11 @@
      */
     protected void adjustAppearance(@NonNull WindowState dimmingContainer,
                                     float alpha, int blurRadius) {
+        if (!mHost.isVisibleRequested()) {
+            // If the host is already going away, there is no point in keeping dimming
+            return;
+        }
+
         if (mDimState != null || (alpha != 0 || blurRadius != 0)) {
             final DimState d = obtainDimState(dimmingContainer);
             d.prepareLookChange(alpha, blurRadius);
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index eafc8be..016ceba 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -24,6 +24,10 @@
 
 import android.annotation.NonNull;
 import android.graphics.Bitmap;
+import android.graphics.PixelFormat;
+import android.hardware.HardwareBuffer;
+import android.media.Image;
+import android.media.ImageReader;
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -33,10 +37,12 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.TransitionAnimation;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
 import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
+import com.android.window.flags.Flags;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -400,23 +406,20 @@
                 Slog.e(TAG, "Invalid task snapshot hw buffer, taskId=" + mId);
                 return false;
             }
-            final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
-                    mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace());
-            if (bitmap == null) {
-                Slog.e(TAG, "Invalid task snapshot hw bitmap");
-                return false;
-            }
 
-            final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */);
+            final HardwareBuffer hwBuffer = mSnapshot.getHardwareBuffer();
+            final int width = hwBuffer.getWidth();
+            final int height = hwBuffer.getHeight();
+            final int pixelFormat = hwBuffer.getFormat();
+            final Bitmap swBitmap = !Flags.reduceTaskSnapshotMemoryUsage()
+                    || (pixelFormat != PixelFormat.RGB_565 && pixelFormat != PixelFormat.RGBA_8888)
+                    || !mSnapshot.isRealSnapshot()
+                    || TransitionAnimation.hasProtectedContent(hwBuffer)
+                    ? copyToSwBitmapReadBack()
+                    : copyToSwBitmapDirect(width, height, pixelFormat);
             if (swBitmap == null) {
-                Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig() + ", isMutable="
-                        + bitmap.isMutable() + ") to (config=ARGB_8888, isMutable=false) failed.");
                 return false;
             }
-            final int width = bitmap.getWidth();
-            final int height = bitmap.getHeight();
-            bitmap.recycle();
-
             final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
             try (FileOutputStream fos = new FileOutputStream(file)) {
                 swBitmap.compress(JPEG, COMPRESS_QUALITY, fos);
@@ -448,6 +451,58 @@
             return true;
         }
 
+        private Bitmap copyToSwBitmapReadBack() {
+            final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
+                    mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace());
+            if (bitmap == null) {
+                Slog.e(TAG, "Invalid task snapshot hw bitmap");
+                return null;
+            }
+
+            final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */);
+            if (swBitmap == null) {
+                Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig()
+                        + ", isMutable=" + bitmap.isMutable()
+                        + ") to (config=ARGB_8888, isMutable=false) failed.");
+                return null;
+            }
+            bitmap.recycle();
+            return swBitmap;
+        }
+
+        /**
+         * Use ImageReader to create the software bitmap, so SkImage won't create an extra texture.
+         */
+        private Bitmap copyToSwBitmapDirect(int width, int height, int pixelFormat) {
+            try (ImageReader ir = ImageReader.newInstance(width, height,
+                    pixelFormat, 1 /* maxImages */)) {
+                ir.getSurface().attachAndQueueBufferWithColorSpace(mSnapshot.getHardwareBuffer(),
+                        mSnapshot.getColorSpace());
+                try (Image image = ir.acquireLatestImage()) {
+                    if (image == null || image.getPlaneCount() < 1) {
+                        Slog.e(TAG, "Image reader cannot acquire image");
+                        return null;
+                    }
+
+                    final Image.Plane[] planes = image.getPlanes();
+                    if (planes.length != 1) {
+                        Slog.e(TAG, "Image reader cannot get plane");
+                        return null;
+                    }
+                    final Image.Plane plane = planes[0];
+                    final int rowPadding = plane.getRowStride() - plane.getPixelStride()
+                            * image.getWidth();
+                    final Bitmap swBitmap = Bitmap.createBitmap(
+                            image.getWidth() + rowPadding / plane.getPixelStride() /* width */,
+                            image.getHeight() /* height */,
+                            pixelFormat == PixelFormat.RGB_565
+                                    ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888);
+                    swBitmap.copyPixelsFromBuffer(plane.getBuffer());
+                    return swBitmap;
+                }
+            }
+        }
+
         @Override
         public boolean equals(Object o) {
             if (o == null || getClass() != o.getClass()) return false;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8587b5a..0531828 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1832,6 +1832,17 @@
                 && supportsMultiWindowInDisplayArea(tda);
     }
 
+    /** Returns true if the task bounds should persist across power cycles. */
+    private static boolean persistTaskBounds(@NonNull WindowConfiguration configuration) {
+        return configuration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+    }
+
+    /** Returns true if the nested task is allowed to have independent bounds from its parent. */
+    private static boolean allowIndependentBoundsFromParent(
+            @NonNull WindowConfiguration configuration) {
+        return configuration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+    }
+
     /**
      * Check whether this task can be launched on the specified display.
      *
@@ -1996,11 +2007,11 @@
     private void onConfigurationChangedInner(Configuration newParentConfig) {
         // Check if the new configuration supports persistent bounds (eg. is Freeform) and if so
         // restore the last recorded non-fullscreen bounds.
-        final boolean prevPersistTaskBounds = getWindowConfiguration().persistTaskBounds();
-        boolean nextPersistTaskBounds =
-                getRequestedOverrideConfiguration().windowConfiguration.persistTaskBounds();
+        final boolean prevPersistTaskBounds = persistTaskBounds(getWindowConfiguration());
+        boolean nextPersistTaskBounds = persistTaskBounds(
+                getRequestedOverrideConfiguration().windowConfiguration);
         if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_UNDEFINED) {
-            nextPersistTaskBounds = newParentConfig.windowConfiguration.persistTaskBounds();
+            nextPersistTaskBounds = persistTaskBounds(newParentConfig.windowConfiguration);
         }
         // Only restore to the last non-fullscreen bounds when the requested override bounds
         // have not been explicitly set already.
@@ -2042,7 +2053,7 @@
 
         // If the configuration supports persistent bounds (eg. Freeform), keep track of the
         // current (non-fullscreen) bounds for persistence.
-        if (getWindowConfiguration().persistTaskBounds()) {
+        if (persistTaskBounds(getWindowConfiguration())) {
             final Rect currentBounds = getRequestedOverrideBounds();
             if (!currentBounds.isEmpty()) {
                 setLastNonFullscreenBounds(currentBounds);
@@ -2383,33 +2394,48 @@
     }
 
     void updateOverrideConfigurationFromLaunchBounds() {
-        // If the task is controlled by another organized task, do not set override
-        // configurations and let its parent (organized task) to control it;
         final Task rootTask = getRootTask();
-        boolean shouldInheritBounds = rootTask != this && rootTask.isOrganized();
-        if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
-            // Only inherit from organized parent when this task is not organized.
-            shouldInheritBounds &= !isOrganized();
-        }
-        final Rect bounds = shouldInheritBounds ? null : getLaunchBounds();
-        setBounds(bounds);
-    }
-
-    /** Returns the bounds that should be used to launch this task. */
-    Rect getLaunchBounds() {
-        final Task rootTask = getRootTask();
-        if (rootTask == null) {
-            return null;
-        }
-
+        final boolean hasParentTask = rootTask != this;
         final int windowingMode = getWindowingMode();
-        if (!isActivityTypeStandardOrUndefined()
-                || windowingMode == WINDOWING_MODE_FULLSCREEN) {
-            return isResizeable() ? rootTask.getRequestedOverrideBounds() : null;
-        } else if (!getWindowConfiguration().persistTaskBounds()) {
-            return rootTask.getRequestedOverrideBounds();
+        final boolean isNonStandardOrFullscreen = !isActivityTypeStandardOrUndefined()
+                || windowingMode == WINDOWING_MODE_FULLSCREEN;
+        if (!Flags.nestedTasksWithIndependentBounds()
+                && !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
+            final Rect bounds;
+            if (hasParentTask && rootTask.isOrganized()) {
+                bounds = null;
+            } else if (isNonStandardOrFullscreen) {
+                bounds = isResizeable() ? rootTask.getRequestedOverrideBounds() : null;
+            } else if (!persistTaskBounds(getWindowConfiguration())) {
+                bounds = rootTask.getRequestedOverrideBounds();
+            } else {
+                bounds = mLastNonFullscreenBounds;
+            }
+            setBounds(bounds);
+            return;
         }
-        return mLastNonFullscreenBounds;
+
+        // Non-standard/fullscreen unresizable tasks should always inherit.
+        boolean shouldInheritBounds = isNonStandardOrFullscreen && !isResizeable();
+        // Task itself is not organized (e.g. Home), just inherit from its organized parent.
+        shouldInheritBounds |= hasParentTask && rootTask.isOrganized() && !isOrganized();
+        // Nested tasks should inherit when they're not allowed to have independent bounds, such as
+        // in multi-window split-screen.
+        shouldInheritBounds |= hasParentTask
+                && !(allowIndependentBoundsFromParent(getWindowConfiguration())
+                && persistTaskBounds(getWindowConfiguration()));
+        if (shouldInheritBounds) {
+            setBounds(null);
+            return;
+        }
+        if (!hasParentTask && !persistTaskBounds(getWindowConfiguration())) {
+            // Non-nested, non-persistable tasks such as PIP or multi-window floating windows.
+            return;
+        }
+        // Non-nested, persisted tasks (e.g. top-level freeform) or nested persisted tasks that
+        // allow independent bounds from parent (e.g. nested freeform) should use launch-params
+        // bounds set to |mLastNonFullscreenBounds|.
+        setBounds(mLastNonFullscreenBounds);
     }
 
     void setRootProcess(WindowProcessController proc) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2c10af4..65001f4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -3401,7 +3401,6 @@
      * Applies the new configuration for the changed displays. Returns the activities that should
      * check whether to deliver the new configuration to clients.
      */
-    @Nullable
     void applyDisplayChangeIfNeeded(@NonNull ArraySet<WindowContainer<?>> activitiesMayChange) {
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             final WindowContainer<?> wc = mParticipants.valueAt(i);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index bdd1372..d356128 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -337,6 +337,11 @@
     public static final int ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE = 1 << 25;
     public static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff;
 
+    private static final int ACTIVITY_STATE_VISIBLE =
+            com.android.window.flags.Flags.useVisibleRequestedForProcessTracker()
+                    ? ACTIVITY_STATE_FLAG_IS_VISIBLE
+                    : ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE;
+
     /**
      * The state for oom-adjustment calculation. The higher 16 bits are the activity states, and the
      * lower 16 bits are the task layer rank (see {@link Task#mLayerRank}). This field is written by
@@ -1260,8 +1265,7 @@
         int nonOccludedRatio = 0;
         long perceptibleTaskStoppedTimeMillis = Long.MIN_VALUE;
         final boolean wasResumed = hasResumedActivity();
-        final boolean wasAnyVisible = (mActivityStateFlags
-                & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
+        final boolean wasAnyVisible = (mActivityStateFlags & ACTIVITY_STATE_VISIBLE) != 0;
         for (int i = mActivities.size() - 1; i >= 0; i--) {
             final ActivityRecord r = mActivities.get(i);
             if (r.isVisible()) {
@@ -1275,8 +1279,9 @@
             if (task.mLayerRank != Task.LAYER_RANK_INVISIBLE) {
                 stateFlags |= ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK;
             }
+            final ActivityRecord.State state = r.getState();
             if (r.isVisibleRequested()) {
-                if (r.isState(RESUMED)) {
+                if (state == RESUMED) {
                     stateFlags |= ACTIVITY_STATE_FLAG_HAS_RESUMED;
                     final int windowingMode = r.getWindowingMode();
                     if (windowingMode == WINDOWING_MODE_MULTI_WINDOW
@@ -1301,13 +1306,21 @@
                 // this process, we'd find out the one with the minimal layer, thus it'll
                 // get a higher adj score.
             } else if (!visible && bestInvisibleState != PAUSING) {
-                if (r.isState(PAUSING, PAUSED)) {
+                if (state == PAUSING) {
                     bestInvisibleState = PAUSING;
-                } else if (r.isState(STOPPING)) {
+                    // Treat PAUSING as visible in case the next activity in the same process has
+                    // not yet been set as visible-requested.
+                    if (com.android.window.flags.Flags.useVisibleRequestedForProcessTracker()
+                            && r.isVisible()) {
+                        stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE;
+                    }
+                } else if (state == PAUSED) {
+                    bestInvisibleState = PAUSED;
+                } else if (state == STOPPING) {
                     bestInvisibleState = STOPPING;
                     // Not "finishing" if any of activity isn't finishing.
                     allStoppingFinishing &= r.finishing;
-                } else if (bestInvisibleState == DESTROYED && r.isState(STOPPED)) {
+                } else if (bestInvisibleState == DESTROYED && state == STOPPED) {
                     if (task.mIsPerceptible) {
                         perceptibleTaskStoppedTimeMillis =
                                 Long.max(r.mStoppedTime, perceptibleTaskStoppedTimeMillis);
@@ -1340,7 +1353,7 @@
         stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
         if (visible) {
             stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE;
-        } else if (bestInvisibleState == PAUSING) {
+        } else if (bestInvisibleState == PAUSING || bestInvisibleState == PAUSED) {
             stateFlags |= ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED;
         } else if (bestInvisibleState == STOPPING) {
             stateFlags |= ACTIVITY_STATE_FLAG_IS_STOPPING;
@@ -1351,8 +1364,7 @@
         mActivityStateFlags = stateFlags;
         mPerceptibleTaskStoppedTimeMillis = perceptibleTaskStoppedTimeMillis;
 
-        final boolean anyVisible = (stateFlags
-                & (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
+        final boolean anyVisible = (stateFlags & ACTIVITY_STATE_VISIBLE) != 0;
         if (!wasAnyVisible && anyVisible) {
             mAtm.mVisibleActivityProcessTracker.onAnyActivityVisible(this);
             mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, true /*visible*/);
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index f6b107b..d60807c 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -62,6 +62,8 @@
                         Collectors.toSet())).size(); // Dedupe type strings
         mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
         mPrepareGetCredentialCallback = prepareGetCredentialCallback;
+
+        Slog.i(TAG, "PrepareGetRequestSession constructed.");
     }
 
     @Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 51ed6bb..f055feb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -276,6 +276,7 @@
 import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
 import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
 import static com.android.server.devicepolicy.DevicePolicyEngine.DEFAULT_POLICY_SIZE_LIMIT;
+import static com.android.server.devicepolicy.DevicePolicyEngine.SYSTEM_SUPERVISION_ROLE;
 import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE;
 import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__COPE;
 import static com.android.server.devicepolicy.DevicePolicyStatsLog.DEVICE_POLICY_MANAGEMENT_MODE__MANAGEMENT_MODE__DEVICE_OWNER;
@@ -16296,6 +16297,13 @@
         return null;
     }
 
+    /**
+     * When multiple admins enforce a policy, this method returns an admin according to this order:
+     * 1. Supervision
+     * 2. DPC
+     *
+     * Otherwise, it returns any other admin.
+     */
     private android.app.admin.EnforcingAdmin getEnforcingAdminInternal(int userId,
             String identifier) {
         Objects.requireNonNull(identifier);
@@ -16304,16 +16312,22 @@
         if (admins.isEmpty()) {
             return null;
         }
-
-        final EnforcingAdmin admin;
         if (admins.size() == 1) {
-            admin = admins.iterator().next();
-        } else {
-            Optional<EnforcingAdmin> dpc = admins.stream()
-                    .filter(a -> a.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)).findFirst();
-            admin = dpc.orElseGet(() -> admins.stream().findFirst().get());
+            return admins.iterator().next().getParcelableAdmin();
         }
-        return admin == null ? null : admin.getParcelableAdmin();
+        Optional<EnforcingAdmin> supervision = admins.stream()
+                .filter(a -> a.hasAuthority(
+                        EnforcingAdmin.getRoleAuthorityOf(SYSTEM_SUPERVISION_ROLE)))
+                .findFirst();
+        if (supervision.isPresent()) {
+            return supervision.get().getParcelableAdmin();
+        }
+        Optional<EnforcingAdmin> dpc = admins.stream()
+                .filter(a -> a.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)).findFirst();
+        if (dpc.isPresent()) {
+            return dpc.get().getParcelableAdmin();
+        }
+        return admins.iterator().next().getParcelableAdmin();
     }
 
     private <V> Set<EnforcingAdmin> getEnforcingAdminsForIdentifier(int userId, String identifier) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 543e32f..9ff6eb6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -34,6 +34,7 @@
 import android.app.admin.PolicyKey;
 import android.app.admin.PolicyValue;
 import android.app.admin.UserRestrictionPolicyKey;
+import android.app.admin.flags.Flags;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentFilter;
@@ -282,7 +283,9 @@
 
     static PolicyDefinition<Set<String>> PERMITTED_INPUT_METHODS = new PolicyDefinition<>(
             new NoArgsPolicyKey(DevicePolicyIdentifiers.PERMITTED_INPUT_METHODS_POLICY),
-            new MostRecent<>(),
+            (Flags.usePolicyIntersectionForPermittedInputMethods()
+                ? new StringSetIntersection()
+                : new MostRecent<>()),
             POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE,
             PolicyEnforcerCallbacks::noOp,
             new PackageSetPolicySerializer());
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java
new file mode 100644
index 0000000..bc075b02
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetIntersection.java
@@ -0,0 +1,59 @@
+/*
+ * 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.server.devicepolicy;
+
+import android.annotation.NonNull;
+import android.app.admin.PolicyValue;
+import android.app.admin.PackageSetPolicyValue;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Objects;
+import java.util.Set;
+
+final class StringSetIntersection extends ResolutionMechanism<Set<String>> {
+
+    @Override
+    PolicyValue<Set<String>> resolve(
+            @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<Set<String>>> adminPolicies) {
+        Objects.requireNonNull(adminPolicies);
+        Set<String> intersectionOfPolicies = null;
+        for (PolicyValue<Set<String>> policy : adminPolicies.values()) {
+            if (intersectionOfPolicies == null) {
+                intersectionOfPolicies = new HashSet<>(policy.getValue());
+            } else {
+                intersectionOfPolicies.retainAll(policy.getValue());
+            }
+        }
+        if (intersectionOfPolicies == null) {
+            return null;
+        }
+        // Note that the resulting set below may be empty, but that's fine:
+        // particular policy should decide what is the meaning of an empty set.
+        return new PackageSetPolicyValue(intersectionOfPolicies);
+    }
+
+    @Override
+    android.app.admin.StringSetIntersection getParcelableResolutionMechanism() {
+        return new android.app.admin.StringSetIntersection();
+    }
+
+    @Override
+    public String toString() {
+        return "StringSetIntersection {}";
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 987b9c6..3289d70b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -680,6 +680,67 @@
                 ApplicationStartInfo.START_TIMESTAMP_FORK));
     }
 
+    /**
+     * Test that cleanup old records works as expected, removing records that are older than the max
+     * retention length.
+     */
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_APP_START_INFO_CLEANUP_OLD_RECORDS)
+    public void testOldRecordsCleanup() throws Exception {
+        // Use a different start timestamp for each record so we can identify which was removed.
+        // This timestamp is not used for ordering and has no impact on removal.
+        final long startTimeRecord1 = 123L;
+        final long startTimeRecord2 = 456L;
+        final long startTimeRecord3 = 789L;
+
+        // Create a process record to use with all starts.
+        ProcessRecord app = makeProcessRecord(
+                APP_1_PID_1,                     // pid
+                APP_1_UID,                       // uid
+                APP_1_UID,                       // packageUid
+                null,                            // definingUid
+                APP_1_PROCESS_NAME,              // processName
+                APP_1_PACKAGE_NAME);             // packageName
+
+        // Set monotonic time to 1, and then trigger a start info record.
+        doReturn(1L).when(mAppStartInfoTracker).getMonotonicTimeMs();
+        mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord1, app,
+                buildIntent(COMPONENT), false /* isAlarm */);
+
+        // Set monotonic time to 2, and then trigger another start info record.
+        doReturn(2L).when(mAppStartInfoTracker).getMonotonicTimeMs();
+        mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord2, app,
+                buildIntent(COMPONENT), false /* isAlarm */);
+
+        // Set monotonic time to 3, then trigger another start info record.
+        doReturn(3L).when(mAppStartInfoTracker).getMonotonicTimeMs();
+        mAppStartInfoTracker.handleProcessBroadcastStart(startTimeRecord3, app,
+                buildIntent(COMPONENT), false /* isAlarm */);
+
+        // Verify that all 3 records were added successfully.
+        ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
+        mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);
+        assertEquals(3, list.size());
+        assertEquals(startTimeRecord3, list.get(0).getStartupTimestamps().get(0).longValue());
+        assertEquals(startTimeRecord2, list.get(1).getStartupTimestamps().get(0).longValue());
+        assertEquals(startTimeRecord1, list.get(2).getStartupTimestamps().get(0).longValue());
+
+        // Set monotonic time to max history length + 3 so that the older 2 records will be removed
+        // but that newest 1 will remain.
+        doReturn(AppStartInfoTracker.APP_START_INFO_HISTORY_LENGTH_MS + 3L)
+                .when(mAppStartInfoTracker).getMonotonicTimeMs();
+
+        // Trigger a persist which will trigger the cleanup of old records.
+        mAppStartInfoTracker.persistProcessStartInfo();
+
+        // Now verify that the records older than max were removed, and that the records not older
+        // remain.
+        list.clear();
+        mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);
+        assertEquals(1, list.size());
+        assertEquals(startTimeRecord3, list.get(0).getStartupTimestamps().get(0).longValue());
+    }
+
     private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
         try {
             Field field = clazz.getDeclaredField(fieldName);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 8c09f26..fdf78ad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -56,12 +56,12 @@
 import android.app.AppGlobals;
 import android.app.IActivityManager;
 import android.app.UiModeManager;
+import android.app.compat.CompatChanges;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
 import android.app.job.JobWorkItem;
 import android.app.usage.UsageStatsManagerInternal;
-import android.compat.testing.PlatformCompatChangeRule;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -81,6 +81,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
@@ -98,6 +99,7 @@
 import com.android.server.LocalServices;
 import com.android.server.PowerAllowlistInternal;
 import com.android.server.SystemServiceManager;
+import com.android.server.compat.PlatformCompat;
 import com.android.server.job.controllers.ConnectivityController;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.job.controllers.QuotaController;
@@ -106,14 +108,10 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.usage.AppStandbyInternal;
 
-import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
-import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TestRule;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
@@ -147,9 +145,6 @@
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
-    @Rule
-    public TestRule compatChangeRule = new PlatformCompatChangeRule();
-
     private ChargingPolicyChangeListener mChargingPolicyChangeListener;
 
     private int mSourceUid;
@@ -166,8 +161,10 @@
         mMockingSession = mockitoSession()
                 .initMocks(this)
                 .strictness(Strictness.LENIENT)
+                .mockStatic(CompatChanges.class)
                 .mockStatic(LocalServices.class)
                 .mockStatic(PermissionChecker.class)
+                .mockStatic(ServiceManager.class)
                 .startMocking();
 
         // Called in JobSchedulerService constructor.
@@ -230,6 +227,9 @@
         ArgumentCaptor<ChargingPolicyChangeListener> chargingPolicyChangeListenerCaptor =
                 ArgumentCaptor.forClass(ChargingPolicyChangeListener.class);
 
+        doReturn(mock(PlatformCompat.class))
+                .when(() -> ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
         mService = new TestJobSchedulerService(mContext);
         mService.waitOnAsyncLoadingForTesting();
 
@@ -1074,12 +1074,15 @@
      */
     @Test
     @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
-    @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
     public void testGetRescheduleJobForFailure_abandonedJob() {
         final long nowElapsed = sElapsedRealtimeClock.millis();
         final long initialBackoffMs = MINUTE_IN_MILLIS;
         mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3;
 
+        // Mock the OVERRIDE_HANDLE_ABANDONED_JOBS compat change overrides.
+        when(CompatChanges.isChangeEnabled(
+                eq(JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS), anyInt())).thenReturn(false);
+
         JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
                 createJobInfo()
                         .setBackoffCriteria(initialBackoffMs, JobInfo.BACKOFF_POLICY_LINEAR));
@@ -1148,8 +1151,10 @@
      */
     @Test
     @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
-    @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
     public void testGetRescheduleJobForFailure_EnableFlagDisableCompatCheckAggressiveBackoff() {
+        // Mock the OVERRIDE_HANDLE_ABANDONED_JOBS compat change overrides.
+        when(CompatChanges.isChangeEnabled(
+                eq(JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS), anyInt())).thenReturn(false);
         assertFalse(mService.shouldUseAggressiveBackoff(
                         mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF - 1,
                         mSourceUid));
@@ -1167,8 +1172,10 @@
      */
     @Test
     @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
-    @EnableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
     public void testGetRescheduleJobForFailure_EnableFlagEnableCompatCheckAggressiveBackoff() {
+        // Mock the OVERRIDE_HANDLE_ABANDONED_JOBS compat change overrides.
+        when(CompatChanges.isChangeEnabled(
+                eq(JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS), anyInt())).thenReturn(true);
         assertFalse(mService.shouldUseAggressiveBackoff(
                         mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF - 1,
                         mSourceUid));
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 2d84887..924fe95 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -76,6 +76,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
@@ -93,6 +94,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.PowerAllowlistInternal;
+import com.android.server.compat.PlatformCompat;
 import com.android.server.job.Flags;
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.job.JobSchedulerService;
@@ -104,8 +106,6 @@
 import com.android.server.job.controllers.QuotaController.TimingSession;
 import com.android.server.usage.AppStandbyInternal;
 
-import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -168,6 +168,8 @@
     private PowerAllowlistInternal mPowerAllowlistInternal;
     @Mock
     private UsageStatsManagerInternal mUsageStatsManager;
+    @Mock
+    private PlatformCompat mPlatformCompat;
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule =
@@ -182,6 +184,7 @@
                 .strictness(Strictness.LENIENT)
                 .spyStatic(DeviceConfig.class)
                 .mockStatic(LocalServices.class)
+                .mockStatic(ServiceManager.class)
                 .startMocking();
 
         // Called in StateController constructor.
@@ -198,6 +201,7 @@
         }
         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
         when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+
         doReturn(mActivityMangerInternal)
                 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
         doReturn(mock(AppStandbyInternal.class))
@@ -253,6 +257,8 @@
                 ArgumentCaptor.forClass(PowerAllowlistInternal.TempAllowlistChangeListener.class);
         ArgumentCaptor<UsageStatsManagerInternal.UsageEventListener> ueListenerCaptor =
                 ArgumentCaptor.forClass(UsageStatsManagerInternal.UsageEventListener.class);
+        doReturn(mPlatformCompat)
+                .when(() -> ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
         mQuotaController = new QuotaController(mJobSchedulerService,
                 mock(BackgroundJobsController.class), mock(ConnectivityController.class));
 
@@ -5591,13 +5597,17 @@
     }
 
     @Test
-    @EnableCompatChanges({QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
-            QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS})
     @RequiresFlagsEnabled({Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS,
             Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS})
     public void testTracking_OutOfQuota_ForegroundAndBackground_CompactChangeOverrides() {
         setDischarging();
 
+        // Mock the OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS compat change overrides.
+        doReturn(true).when(mPlatformCompat).isChangeEnabledByUid(
+                eq(QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS), anyInt());
+        doReturn(true).when(mPlatformCompat).isChangeEnabledByUid(
+                eq(QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS), anyInt());
+
         JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
         JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
         trackJobs(jobBg, jobTop);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index fc864dd..3ed4a52 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -343,12 +343,13 @@
                 assertThat(mReadFiles).containsExactly("123.bh", "1000.bh");
             } else if (item.eventCode == HistoryItem.EVENT_ALARM) {
                 eventsRead++;
-                assertThat(mReadFiles).containsExactly("123.bh", "1000.bh", "2000.bh");
+                // This event is in the current buffer, so 2000.bh shouldn't be read from disk
+                assertThat(mReadFiles).containsExactly("123.bh", "1000.bh");
             }
         }
 
         assertThat(eventsRead).isEqualTo(3);
-        assertThat(mReadFiles).containsExactly("123.bh", "1000.bh", "2000.bh", "3000.bh");
+        assertThat(mReadFiles).containsExactly("123.bh", "1000.bh");
     }
 
     @Test
@@ -366,34 +367,41 @@
             return invocation.callRealMethod();
         }).when(mHistory).readFragmentToParcel(any(), any());
 
-        BatteryStatsHistoryIterator iterator = mHistory.iterate(1000, 3000);
+        int eventsRead = 0;
+        BatteryStatsHistoryIterator iterator = mHistory.iterate(1001, 3000);
         while (iterator.hasNext()) {
             HistoryItem item = iterator.next();
             if (item.eventCode == HistoryItem.EVENT_JOB_START) {
                 fail("Event outside the range");
             } else if (item.eventCode == HistoryItem.EVENT_JOB_FINISH) {
+                eventsRead++;
                 assertThat(mReadFiles).containsExactly("1000.bh");
             } else if (item.eventCode == HistoryItem.EVENT_ALARM) {
                 fail("Event outside the range");
             }
         }
 
-        assertThat(mReadFiles).containsExactly("1000.bh", "2000.bh");
+        assertThat(eventsRead).isEqualTo(1);
+        assertThat(mReadFiles).containsExactly("1000.bh");
     }
 
     private void prepareMultiFileHistory() {
-        mClock.realtime = 1000;
-        mClock.uptime = 1000;
+        mClock.realtime = 500;
+        mClock.uptime = 500;
         mHistory.recordEvent(mClock.realtime, mClock.uptime,
                 BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42);
 
+        mClock.realtime = 1000;
+        mClock.uptime = 1000;
         mHistory.startNextFragment(mClock.realtime);       // 1000.bh
 
-        mClock.realtime = 2000;
-        mClock.uptime = 2000;
+        mClock.realtime = 1500;
+        mClock.uptime = 1500;
         mHistory.recordEvent(mClock.realtime, mClock.uptime,
                 BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42);
 
+        mClock.realtime = 2000;
+        mClock.uptime = 2000;
         mHistory.startNextFragment(mClock.realtime);       // 2000.bh
 
         mClock.realtime = 3000;
@@ -401,8 +409,8 @@
         mHistory.recordEvent(mClock.realtime, mClock.uptime,
                 HistoryItem.EVENT_ALARM, "alarm", 42);
 
-        // Flush accumulated history to disk
-        mHistory.startNextFragment(mClock.realtime);
+        // Back up accumulated history to disk
+        mHistory.writeHistory();
     }
 
     private void verifyActiveFile(BatteryStatsHistory history, String file) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
index 3565244..33529c3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -207,6 +208,23 @@
                 eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH), any());
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_HEARING_INPUT_CHANGE_WHEN_COMM_DEVICE)
+    public void onCallStateChanged_offHookMultiple_addListenerOnlyOneTime() {
+        AudioDeviceInfo a2dpDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+                AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+        when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+                new AudioDeviceInfo[]{a2dpDeviceInfo});
+        when(mAudioManager.getCommunicationDevice()).thenReturn(a2dpDeviceInfo);
+
+        mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+        mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+
+        verify(mAudioManager, times(1)).addOnCommunicationDeviceChangedListener(
+                any(Executor.class),
+                any(AudioManager.OnCommunicationDeviceChangedListener.class));
+    }
+
     private AudioDeviceInfo createAudioDeviceInfo(String address, int type) {
         AudioDevicePort audioDevicePort = mock(AudioDevicePort.class);
         doReturn(type).when(audioDevicePort).type();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index b2d48a7..2349120 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -226,13 +226,16 @@
      */
     protected void adoptFullVolumeBehaviorOnAvbCapableAudioOutputDevices() {
         if (getDeviceType() == HdmiDeviceInfo.DEVICE_PLAYBACK) {
-            mAudioManager.setDeviceVolumeBehavior(HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI,
-                    AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+            mAudioDeviceVolumeManager.setDeviceVolumeBehavior(
+                    HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI,
+                    AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
         } else if (getDeviceType() == HdmiDeviceInfo.DEVICE_TV) {
-            mAudioManager.setDeviceVolumeBehavior(HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC,
-                    AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
-            mAudioManager.setDeviceVolumeBehavior(HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_EARC,
-                    AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+            mAudioDeviceVolumeManager.setDeviceVolumeBehavior(
+                    HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC,
+                    AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+            mAudioDeviceVolumeManager.setDeviceVolumeBehavior(
+                    HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_EARC,
+                    AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
         }
     }
 
@@ -307,8 +310,9 @@
                 INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(),
                 INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute());
 
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
     }
 
     protected void enableAdjustOnlyAbsoluteVolumeBehavior() {
@@ -320,8 +324,9 @@
                 INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(),
                 INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute());
 
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
     }
 
     protected void verifyGiveAudioStatusNeverSent() {
@@ -419,14 +424,16 @@
         receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
 
         // AVB should not be enabled before receiving <Report Audio Status>
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
 
         receiveReportAudioStatus(60, false);
 
         // Check that absolute volume behavior was the last one adopted
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
 
         // Check that the volume and mute status received were included when setting AVB
         verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeBehavior(
@@ -447,19 +454,22 @@
         enableSystemAudioModeIfNeeded();
         receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
 
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
         receiveReportAudioStatus(127, false);
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
     }
 
     @Test
     public void avbEnabled_standby_avbDisabled() {
         enableAbsoluteVolumeBehavior();
         mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
     }
 
     @Test
@@ -468,8 +478,9 @@
 
         setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_DISABLED);
 
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
     }
 
     @Test
@@ -477,8 +488,9 @@
         enableAbsoluteVolumeBehavior();
 
         receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
     }
 
     @Test
@@ -489,8 +501,9 @@
                 getSystemAudioDeviceLogicalAddress(), getLogicalAddress(),
                 Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_UNRECOGNIZED_OPCODE));
         mTestLooper.dispatchAll();
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
     }
 
     @Test
@@ -501,8 +514,9 @@
         enableAbsoluteVolumeBehavior();
 
         receiveSetSystemAudioMode(false);
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BasePlaybackDeviceAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BasePlaybackDeviceAvbTest.java
index 4c12e436..7c7e220 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BasePlaybackDeviceAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BasePlaybackDeviceAvbTest.java
@@ -20,7 +20,7 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.media.AudioDeviceAttributes;
-import android.media.AudioManager;
+import android.media.AudioDeviceVolumeManager;
 
 import org.junit.Test;
 
@@ -60,8 +60,8 @@
      */
     @Test
     public void savlNotSupported_allOtherConditionsMet_giveAudioStatusNotSent() {
-        mAudioManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        mAudioDeviceVolumeManager.setDeviceVolumeBehavior(getAudioOutputDevice(),
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
         setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
         enableSystemAudioModeIfNeeded();
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
index f44517a..7a43598 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
@@ -96,13 +96,15 @@
         receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
 
         // Adjust-only AVB should not be enabled before receiving <Report Audio Status>
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
 
         receiveReportAudioStatus(20, false);
 
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
 
         verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeAdjustOnlyBehavior(
                 eq(getAudioOutputDevice()),
@@ -124,8 +126,9 @@
 
         receiveReportAudioStatus(40, true);
 
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
 
         verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeAdjustOnlyBehavior(
                 eq(getAudioOutputDevice()),
@@ -149,8 +152,9 @@
 
         receiveReportAudioStatus(40, true);
 
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
 
         verify(mAudioDeviceVolumeManager).setDeviceAbsoluteVolumeAdjustOnlyBehavior(
                 eq(getAudioOutputDevice()),
@@ -283,13 +287,15 @@
         verifyGiveAudioStatusSent();
 
         // The device should use adjust-only AVB while waiting for <Report Audio Status>
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
 
         // The device should switch to AVB upon receiving <Report Audio Status>
         receiveReportAudioStatus(60, false);
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
 
     }
 
@@ -321,8 +327,9 @@
         mTestLooper.dispatchAll();
 
         // The device should not switch away from adjust-only AVB
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
 
         // The device should query support for <Set Audio Volume Level> again
         assertThat(mNativeWrapper.getResultMessages()).contains(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index a4c71bd..2227eeb 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -251,6 +251,35 @@
     }
 
     @Test
+    public void testDeviceSelect_DeviceAssertsActiveSource_singleSetStreamPathMessage() {
+        // TV was watching playback2 device connected at port 2, and wants to select
+        // playback1.
+        TestActionTimer actionTimer = new TestActionTimer();
+        TestCallback callback = new TestCallback();
+        DeviceSelectActionFromTv action = createDeviceSelectAction(actionTimer, callback,
+                /*isCec20=*/false);
+        mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2,
+                "testDeviceSelect");
+        action.start();
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
+        mNativeWrapper.clearResultMessages();
+        mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_1,
+                "testDeviceSelect");
+        mTestLooper.dispatchAll();
+
+        assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_POWER_STATE_CHANGE);
+        action.handleTimerEvent(STATE_WAIT_FOR_POWER_STATE_CHANGE);
+        assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
+        action.processCommand(REPORT_POWER_STATUS_ON);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH);
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+    }
+
+    @Test
     public void testDeviceSelect_DeviceInStandbyStatus_Cec14b() {
         mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2,
                                                  "testDeviceSelect");
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java
index 90f94cb..e07f4f9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioFramework.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.VolumeInfo;
@@ -137,18 +138,6 @@
             // Do nothing
         }
 
-
-        @Override
-        @AudioManager.DeviceVolumeBehavior
-        public int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
-            return mDeviceVolumeBehaviors.getOrDefault(device, DEFAULT_DEVICE_VOLUME_BEHAVIOR);
-        }
-
-        public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
-                @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior) {
-            setVolumeBehaviorHelper(device, deviceVolumeBehavior);
-        }
-
         @Override
         @NonNull
         public List<AudioDeviceAttributes> getDevicesForAttributes(
@@ -186,7 +175,8 @@
                 boolean handlesVolumeAdjustment,
                 @NonNull @CallbackExecutor Executor executor,
                 @NonNull OnAudioDeviceVolumeChangedListener vclistener) {
-            setVolumeBehaviorHelper(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+            setVolumeBehaviorHelper(device,
+                    AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
         }
 
         @Override
@@ -197,7 +187,19 @@
                 @NonNull @CallbackExecutor Executor executor,
                 @NonNull OnAudioDeviceVolumeChangedListener vclistener) {
             setVolumeBehaviorHelper(device,
-                    AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+                    AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY);
+        }
+
+        @Override
+        @AudioDeviceVolumeManager.DeviceVolumeBehavior
+        public int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
+            return mDeviceVolumeBehaviors.getOrDefault(device, DEFAULT_DEVICE_VOLUME_BEHAVIOR);
+        }
+
+        @Override
+        public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
+                @AudioDeviceVolumeManager.DeviceVolumeBehavior int deviceVolumeBehavior) {
+            setVolumeBehaviorHelper(device, deviceVolumeBehavior);
         }
     }
 
@@ -222,7 +224,7 @@
      * Helper method for changing an audio device's volume behavior. Notifies listeners.
      */
     private void setVolumeBehaviorHelper(AudioDeviceAttributes device,
-            @AudioManager.DeviceVolumeBehavior int newVolumeBehavior) {
+            @AudioDeviceVolumeManager.DeviceVolumeBehavior int newVolumeBehavior) {
 
         int currentVolumeBehavior = mDeviceVolumeBehaviors.getOrDefault(
                 device, DEFAULT_DEVICE_VOLUME_BEHAVIOR);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvbTest.java
index 43ab804..ffc1c62 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvbTest.java
@@ -21,7 +21,7 @@
 import android.hardware.hdmi.DeviceFeatures;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
-import android.media.AudioManager;
+import android.media.AudioDeviceVolumeManager;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -64,8 +64,9 @@
 
         // Audio System disables System Audio Mode. AVB should be disabled.
         receiveSetSystemAudioMode(false);
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
 
         // TV reports support for <Set Audio Volume Level>
         mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
@@ -85,7 +86,8 @@
                 false));
         mTestLooper.dispatchAll();
 
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvbTest.java
index 9b343e3..0926180 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvbTest.java
@@ -23,7 +23,7 @@
 import android.hardware.hdmi.DeviceFeatures;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
-import android.media.AudioManager;
+import android.media.AudioDeviceVolumeManager;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
@@ -65,8 +65,9 @@
 
         // Audio System enables System Audio Mode. AVB should be disabled.
         receiveSetSystemAudioMode(true);
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_FULL);
 
         clearInvocations(mAudioManager, mAudioDeviceVolumeManager);
 
@@ -88,7 +89,8 @@
                 false));
         mTestLooper.dispatchAll();
 
-        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
-                AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+        assertThat(mAudioDeviceVolumeManager.getDeviceVolumeBehavior(
+                getAudioOutputDevice())).isEqualTo(
+                AudioDeviceVolumeManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
index 4d2dcf6..43b1ec3 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -67,12 +68,15 @@
     private static final int SESSION_ID_RANGE = ContextHubEndpointManager.SERVICE_SESSION_RANGE;
     private static final int MIN_SESSION_ID = 0;
     private static final int MAX_SESSION_ID = MIN_SESSION_ID + SESSION_ID_RANGE - 1;
+    private static final int SESSION_ID_FOR_OPEN_REQUEST = MAX_SESSION_ID + 1;
+    private static final int INVALID_SESSION_ID_FOR_OPEN_REQUEST = MIN_SESSION_ID + 1;
 
     private static final String ENDPOINT_NAME = "Example test endpoint";
     private static final int ENDPOINT_ID = 1;
     private static final String ENDPOINT_PACKAGE_NAME = "com.android.server.location.contexthub";
 
     private static final String TARGET_ENDPOINT_NAME = "Example target endpoint";
+    private static final String ENDPOINT_SERVICE_DESCRIPTOR = "serviceDescriptor";
     private static final int TARGET_ENDPOINT_ID = 1;
 
     private static final int SAMPLE_MESSAGE_TYPE = 1234;
@@ -225,6 +229,105 @@
     }
 
     @Test
+    public void testEndpointSessionOpenRequest() throws RemoteException {
+        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+        IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+        HubEndpointInfo targetInfo =
+                new HubEndpointInfo(
+                        TARGET_ENDPOINT_NAME,
+                        TARGET_ENDPOINT_ID,
+                        ENDPOINT_PACKAGE_NAME,
+                        Collections.emptyList());
+        mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo});
+        mEndpointManager.onEndpointSessionOpenRequest(
+                SESSION_ID_FOR_OPEN_REQUEST,
+                endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+                targetInfo.getIdentifier(),
+                ENDPOINT_SERVICE_DESCRIPTOR);
+
+        verify(mMockCallback)
+                .onSessionOpenRequest(
+                        SESSION_ID_FOR_OPEN_REQUEST, targetInfo, ENDPOINT_SERVICE_DESCRIPTOR);
+
+        // Accept
+        endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST);
+        verify(mMockEndpointCommunications)
+                .endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST);
+
+        unregisterExampleEndpoint(endpoint);
+    }
+
+    @Test
+    public void testEndpointSessionOpenRequestWithInvalidSessionId() throws RemoteException {
+        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+        IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+        HubEndpointInfo targetInfo =
+                new HubEndpointInfo(
+                        TARGET_ENDPOINT_NAME,
+                        TARGET_ENDPOINT_ID,
+                        ENDPOINT_PACKAGE_NAME,
+                        Collections.emptyList());
+        mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo});
+        mEndpointManager.onEndpointSessionOpenRequest(
+                INVALID_SESSION_ID_FOR_OPEN_REQUEST,
+                endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+                targetInfo.getIdentifier(),
+                ENDPOINT_SERVICE_DESCRIPTOR);
+        verify(mMockEndpointCommunications)
+                .closeEndpointSession(
+                        INVALID_SESSION_ID_FOR_OPEN_REQUEST,
+                        Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
+        verify(mMockCallback, never())
+                .onSessionOpenRequest(
+                        INVALID_SESSION_ID_FOR_OPEN_REQUEST,
+                        targetInfo,
+                        ENDPOINT_SERVICE_DESCRIPTOR);
+
+        unregisterExampleEndpoint(endpoint);
+    }
+
+    @Test
+    public void testEndpointSessionOpenRequest_duplicatedSessionId_noopWhenSessionIsActive()
+            throws RemoteException {
+        assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+        IContextHubEndpoint endpoint = registerExampleEndpoint();
+
+        HubEndpointInfo targetInfo =
+                new HubEndpointInfo(
+                        TARGET_ENDPOINT_NAME,
+                        TARGET_ENDPOINT_ID,
+                        ENDPOINT_PACKAGE_NAME,
+                        Collections.emptyList());
+        mHubInfoRegistry.onEndpointStarted(new HubEndpointInfo[] {targetInfo});
+        mEndpointManager.onEndpointSessionOpenRequest(
+                SESSION_ID_FOR_OPEN_REQUEST,
+                endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+                targetInfo.getIdentifier(),
+                ENDPOINT_SERVICE_DESCRIPTOR);
+        endpoint.openSessionRequestComplete(SESSION_ID_FOR_OPEN_REQUEST);
+        // Now session with id SESSION_ID_FOR_OPEN_REQUEST is active
+
+        // Duplicated session open request
+        mEndpointManager.onEndpointSessionOpenRequest(
+                SESSION_ID_FOR_OPEN_REQUEST,
+                endpoint.getAssignedHubEndpointInfo().getIdentifier(),
+                targetInfo.getIdentifier(),
+                ENDPOINT_SERVICE_DESCRIPTOR);
+
+        // Client API is only invoked once
+        verify(mMockCallback, times(1))
+                .onSessionOpenRequest(
+                        SESSION_ID_FOR_OPEN_REQUEST, targetInfo, ENDPOINT_SERVICE_DESCRIPTOR);
+        // HAL still receives two open complete notifications
+        verify(mMockEndpointCommunications, times(2))
+                .endpointSessionOpenComplete(SESSION_ID_FOR_OPEN_REQUEST);
+
+        unregisterExampleEndpoint(endpoint);
+    }
+
+    @Test
     public void testMessageTransaction() throws RemoteException {
         IContextHubEndpoint endpoint = registerExampleEndpoint();
         testMessageTransactionInternal(endpoint, /* deliverMessageStatus= */ true);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index d1b2e8e..1fb8411 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -274,7 +274,7 @@
     /** Test UserInfo.canHaveProfile for main user */
     @Test
     public void testCanHaveProfile() throws Exception {
-        UserInfo userInfo = createUser(100, FLAG_MAIN, null);
+        UserInfo userInfo = createUser(100, FLAG_FULL | FLAG_MAIN, null);
         assertTrue("Main users can have profile", userInfo.canHaveProfile());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
index c7a06b8..339bac4 100644
--- a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
@@ -259,7 +259,7 @@
 
         AdvancedProtectionProvider provider = new AdvancedProtectionProvider() {
             @Override
-            public List<AdvancedProtectionFeature> getFeatures() {
+            public List<AdvancedProtectionFeature> getFeatures(Context context) {
                 return List.of(feature2);
             }
         };
@@ -291,7 +291,7 @@
 
         AdvancedProtectionProvider provider = new AdvancedProtectionProvider() {
             @Override
-            public List<AdvancedProtectionFeature> getFeatures() {
+            public List<AdvancedProtectionFeature> getFeatures(Context context) {
                 return List.of(feature2);
             }
         };
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 bc8b7be..902171d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -16844,22 +16844,22 @@
     public void updateAutomaticZenRule_implicitRuleWithoutCPS_disallowedFromApp() throws Exception {
         setUpRealZenTest();
         mService.setCallerIsNormalPackage();
-        assertThat(mBinderService.getAutomaticZenRules()).isEmpty();
+        assertThat(mBinderService.getAutomaticZenRules().getList()).isEmpty();
 
         // Create an implicit zen rule by calling setNotificationPolicy from an app.
         mBinderService.setNotificationPolicy(mPkg, new NotificationManager.Policy(0, 0, 0), false);
-        assertThat(mBinderService.getAutomaticZenRules()).hasSize(1);
-        Map.Entry<String, AutomaticZenRule> rule = getOnlyElement(
-                mBinderService.getAutomaticZenRules().entrySet());
-        assertThat(rule.getValue().getOwner()).isNull();
-        assertThat(rule.getValue().getConfigurationActivity()).isNull();
+        assertThat(mBinderService.getAutomaticZenRules().getList()).hasSize(1);
+        AutomaticZenRule.AzrWithId rule = getOnlyElement(
+                (List<AutomaticZenRule.AzrWithId>) mBinderService.getAutomaticZenRules().getList());
+        assertThat(rule.mRule.getOwner()).isNull();
+        assertThat(rule.mRule.getConfigurationActivity()).isNull();
 
         // Now try to update said rule (e.g. disable it). Should fail.
         // We also validate the exception message because NPE could be thrown by all sorts of test
         // issues (e.g. misconfigured mocks).
-        rule.getValue().setEnabled(false);
+        rule.mRule.setEnabled(false);
         NullPointerException e = assertThrows(NullPointerException.class,
-                () -> mBinderService.updateAutomaticZenRule(rule.getKey(), rule.getValue(), false));
+                () -> mBinderService.updateAutomaticZenRule(rule.mId, rule.mRule, false));
         assertThat(e.getMessage()).isEqualTo(
                 "Rule must have a ConditionProviderService and/or configuration activity");
     }
@@ -16869,24 +16869,24 @@
     public void updateAutomaticZenRule_implicitRuleWithoutCPS_allowedFromSystem() throws Exception {
         setUpRealZenTest();
         mService.setCallerIsNormalPackage();
-        assertThat(mBinderService.getAutomaticZenRules()).isEmpty();
+        assertThat(mBinderService.getAutomaticZenRules().getList()).isEmpty();
 
         // Create an implicit zen rule by calling setNotificationPolicy from an app.
         mBinderService.setNotificationPolicy(mPkg, new NotificationManager.Policy(0, 0, 0), false);
-        assertThat(mBinderService.getAutomaticZenRules()).hasSize(1);
-        Map.Entry<String, AutomaticZenRule> rule = getOnlyElement(
-                mBinderService.getAutomaticZenRules().entrySet());
-        assertThat(rule.getValue().getOwner()).isNull();
-        assertThat(rule.getValue().getConfigurationActivity()).isNull();
+        assertThat(mBinderService.getAutomaticZenRules().getList()).hasSize(1);
+        AutomaticZenRule.AzrWithId rule = getOnlyElement(
+                (List<AutomaticZenRule.AzrWithId>) mBinderService.getAutomaticZenRules().getList());
+        assertThat(rule.mRule.getOwner()).isNull();
+        assertThat(rule.mRule.getConfigurationActivity()).isNull();
 
         // Now update said rule from Settings (e.g. disable it). Should work!
         mService.isSystemUid = true;
-        rule.getValue().setEnabled(false);
-        mBinderService.updateAutomaticZenRule(rule.getKey(), rule.getValue(), false);
+        rule.mRule.setEnabled(false);
+        mBinderService.updateAutomaticZenRule(rule.mId, rule.mRule, false);
 
-        Map.Entry<String, AutomaticZenRule> updatedRule = getOnlyElement(
-                mBinderService.getAutomaticZenRules().entrySet());
-        assertThat(updatedRule.getValue().isEnabled()).isFalse();
+        AutomaticZenRule.AzrWithId updatedRule = getOnlyElement(
+                (List<AutomaticZenRule.AzrWithId>) mBinderService.getAutomaticZenRules().getList());
+        assertThat(updatedRule.mRule.isEnabled()).isFalse();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
index fa7dcc8..81d753b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
@@ -35,6 +35,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.pm.ActivityInfo;
@@ -413,7 +414,7 @@
 
         void setDesiredAspectRatio(float aspectRatio) {
             doReturn(aspectRatio).when(getDesktopAppCompatAspectRatioPolicy())
-                    .getDesiredAspectRatio(any());
+                    .getDesiredAspectRatio(any(), anyBoolean());
         }
 
         DesktopAppCompatAspectRatioPolicy getDesktopAppCompatAspectRatioPolicy() {
@@ -422,7 +423,7 @@
 
         float calculateAspectRatio() {
             return getDesktopAppCompatAspectRatioPolicy().calculateAspectRatio(
-                    getTopActivity().getTask());
+                    getTopActivity().getTask(), /* hasOrientationMismatch */ true);
         }
 
         ActivityRecord getTopActivity() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 00b617e..f587d6e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -52,6 +52,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -437,7 +438,7 @@
 
         spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
         doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
-                .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
+                .getDesktopAspectRatioPolicy()).calculateAspectRatio(any(), anyBoolean());
 
         final int desiredWidth =
                 (int) ((LANDSCAPE_DISPLAY_BOUNDS.height() / LETTERBOX_ASPECT_RATIO) + 0.5f);
@@ -933,7 +934,7 @@
 
         spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
         doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
-                .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
+                .getDesktopAspectRatioPolicy()).calculateAspectRatio(any(), anyBoolean());
 
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
@@ -1060,7 +1061,7 @@
 
         spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
         doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
-                .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
+                .getDesktopAspectRatioPolicy()).calculateAspectRatio(any(), anyBoolean());
 
         final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
                 - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
@@ -1115,7 +1116,7 @@
 
         spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
         doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
-                .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
+                .getDesktopAspectRatioPolicy()).calculateAspectRatio(any(), anyBoolean());
 
         final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
                 - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
@@ -1569,7 +1570,7 @@
                 activity.mAppCompatController.getDesktopAspectRatioPolicy();
         spyOn(desktopAppCompatAspectRatioPolicy);
         doReturn(aspectRatio).when(desktopAppCompatAspectRatioPolicy)
-                .getDesiredAspectRatio(any());
+                .getDesiredAspectRatio(any(), anyBoolean());
     }
 
     private void applyUserMinAspectRatioOverride(ActivityRecord activity, int overrideCode,
@@ -1579,7 +1580,7 @@
                 activity.mAppCompatController.getDesktopAspectRatioPolicy();
         spyOn(desktopAppCompatAspectRatioPolicy);
         doReturn(1f).when(desktopAppCompatAspectRatioPolicy)
-                .getDesiredAspectRatio(any());
+                .getDesiredAspectRatio(any(), anyBoolean());
 
         // Enable user aspect ratio settings
         final AppCompatConfiguration appCompatConfiguration =
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 9dc7026..5347f9a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -382,12 +382,17 @@
         assertFalse(tracker.hasResumedActivity(mWpc.mUid));
         assertTrue(mWpc.hasForegroundActivities());
 
-        activity.setVisibility(false);
         activity.setVisibleRequested(false);
-        activity.setState(STOPPED, "test");
-
+        if (com.android.window.flags.Flags.useVisibleRequestedForProcessTracker()) {
+            assertTrue("PAUSING is visible", mWpc.hasVisibleActivities());
+            activity.setState(PAUSED, "test");
+        } else {
+            activity.setVisible(false);
+        }
         verify(tracker).onAllActivitiesInvisible(mWpc);
         assertFalse(mWpc.hasVisibleActivities());
+
+        activity.setState(STOPPED, "test");
         assertFalse(mWpc.hasForegroundActivities());
     }
 
diff --git a/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java b/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
index d9bb7db..5419d94 100644
--- a/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
+++ b/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
@@ -110,7 +110,7 @@
         blockingCallSpeak("foo bar", delegate);
         ArgumentCaptor<SynthesisRequest> req = ArgumentCaptor.forClass(SynthesisRequest.class);
         Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req.capture(),
-                Mockito.<SynthesisCallback>anyObject());
+                Mockito.<SynthesisCallback>any());
 
         assertEquals("eng", req.getValue().getLanguage());
         assertEquals("USA", req.getValue().getCountry());
@@ -133,7 +133,7 @@
         blockingCallSpeak("le fou barre", delegate);
         ArgumentCaptor<SynthesisRequest> req2 = ArgumentCaptor.forClass(SynthesisRequest.class);
         Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req2.capture(),
-                        Mockito.<SynthesisCallback>anyObject());
+                        Mockito.<SynthesisCallback>any());
 
         // The params are basically unchanged.
         assertEquals("eng", req2.getValue().getLanguage());
@@ -177,7 +177,7 @@
         blockingCallSpeak("foo bar", delegate);
         ArgumentCaptor<SynthesisRequest> req = ArgumentCaptor.forClass(SynthesisRequest.class);
         Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req.capture(),
-                Mockito.<SynthesisCallback>anyObject());
+                Mockito.<SynthesisCallback>any());
 
         assertEquals(defaultLocale.getISO3Language(), req.getValue().getLanguage());
         assertEquals(defaultLocale.getISO3Country(), req.getValue().getCountry());
@@ -189,8 +189,8 @@
     private void blockingCallSpeak(String speech, IDelegate mock) throws
             InterruptedException {
         final CountDownLatch latch = new CountDownLatch(1);
-        doCountDown(latch).when(mock).onSynthesizeText(Mockito.<SynthesisRequest>anyObject(),
-                Mockito.<SynthesisCallback>anyObject());
+        doCountDown(latch).when(mock).onSynthesizeText(Mockito.<SynthesisRequest>any(),
+                Mockito.<SynthesisCallback>any());
         mTts.speak(speech, TextToSpeech.QUEUE_ADD, null);
 
         awaitCountDown(latch, 5, TimeUnit.SECONDS);