Merge "[bt] Add the Google module in pixel image" into main
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index 63624d8..8b1a40c 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -55,3 +55,14 @@
     description: "Introduce a new getPendingJobReasonsHistory() API which returns a limited historical view of getPendingJobReasons()."
     bug: "372031023"
 }
+
+
+flag {
+    name: "add_type_info_to_wakelock_tag"
+    namespace: "backstage_power"
+    description: "Append the job type info to wakelock tag"
+    bug: "381880530"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 4335cae..fe6daa5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -5766,6 +5766,41 @@
     }
 
     // Shell command infrastructure
+    int getJobWakelockTag(PrintWriter pw, String pkgName, int userId, @Nullable String namespace,
+            int jobId) {
+        try {
+            final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
+                    userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
+            if (uid < 0) {
+                pw.print("unknown(");
+                pw.print(pkgName);
+                pw.println(")");
+                return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
+            }
+
+            synchronized (mLock) {
+                final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
+                if (DEBUG) {
+                    Slog.d(TAG, "get-job-wakelock-tag " + namespace
+                            + "/" + uid + "/" + jobId + ": " + js);
+                }
+                if (js == null) {
+                    pw.print("unknown(");
+                    UserHandle.formatUid(pw, uid);
+                    pw.print("/jid");
+                    pw.print(jobId);
+                    pw.println(")");
+                    return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
+                }
+
+                pw.println(js.getWakelockTag());
+            }
+        } catch (RemoteException e) {
+            // can't happen
+        }
+        return 0;
+    }
+
     int getJobState(PrintWriter pw, String pkgName, int userId, @Nullable String namespace,
             int jobId) {
         try {
@@ -5945,6 +5980,9 @@
             pw.print(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_HISTORY_API,
                     android.app.job.Flags.getPendingJobReasonsHistoryApi());
             pw.println();
+            pw.print(android.app.job.Flags.FLAG_ADD_TYPE_INFO_TO_WAKELOCK_TAG,
+                    android.app.job.Flags.addTypeInfoToWakelockTag());
+            pw.println();
             pw.decreaseIndent();
             pw.println();
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 42c8250..633598e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -86,6 +86,8 @@
                     return getTransferredNetworkBytes(pw, BYTE_OPTION_DOWNLOAD);
                 case "get-transferred-upload-bytes":
                     return getTransferredNetworkBytes(pw, BYTE_OPTION_UPLOAD);
+                case "get-job-wakelock-tag":
+                    return getJobWakelockTag(pw);
                 case "get-job-state":
                     return getJobState(pw);
                 case "heartbeat":
@@ -424,6 +426,9 @@
             case android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS:
                 pw.println(android.app.job.Flags.jobDebugInfoApis());
                 break;
+            case android.app.job.Flags.FLAG_ADD_TYPE_INFO_TO_WAKELOCK_TAG:
+                pw.println(android.app.job.Flags.addTypeInfoToWakelockTag());
+                break;
             case com.android.server.job.Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS:
                 pw.println(com.android.server.job.Flags.batchActiveBucketJobs());
                 break;
@@ -581,6 +586,49 @@
         }
     }
 
+    private int getJobWakelockTag(PrintWriter pw) throws Exception {
+        checkPermission("get job wakelock tag");
+
+        int userId = UserHandle.USER_SYSTEM;
+        String namespace = null;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-u":
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+
+                case "-n":
+                case "--namespace":
+                    namespace = getNextArgRequired();
+                    break;
+
+                default:
+                    pw.println("Error: unknown option '" + opt + "'");
+                    return -1;
+            }
+        }
+
+        if (userId == UserHandle.USER_CURRENT) {
+            userId = ActivityManager.getCurrentUser();
+        }
+
+        final String pkgName = getNextArgRequired();
+        final String jobIdStr = getNextArgRequired();
+        final int jobId = Integer.parseInt(jobIdStr);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            int ret = mInternal.getJobWakelockTag(pw, pkgName, userId, namespace, jobId);
+            printError(ret, pkgName, userId, namespace, jobId);
+            return ret;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private int getJobState(PrintWriter pw) throws Exception {
         checkPermission("get job state");
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 5a33aa0..4b9d736 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -1459,7 +1459,12 @@
     @NonNull
     public String getWakelockTag() {
         if (mWakelockTag == null) {
-            mWakelockTag = "*job*/" + this.batteryName;
+            mWakelockTag = "*job*";
+            if (android.app.job.Flags.addTypeInfoToWakelockTag()) {
+                mWakelockTag += (isRequestedExpeditedJob()
+                    ? "e" : (getJob().isUserInitiated() ? "u" : "r"));
+            }
+            mWakelockTag += "/" + this.batteryName;
         }
         return mWakelockTag;
     }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d5cb6c0..6c5ac8a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -360,6 +360,7 @@
     field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
     field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
     field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
+    field @FlaggedApi("android.content.pm.uid_based_provider_lookup") public static final String RESOLVE_COMPONENT_FOR_UID = "android.permission.RESOLVE_COMPONENT_FOR_UID";
     field public static final String RESTART_WIFI_SUBSYSTEM = "android.permission.RESTART_WIFI_SUBSYSTEM";
     field @FlaggedApi("android.permission.flags.health_connect_backup_restore_permission_enabled") public static final String RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS = "android.permission.RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS";
     field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS";
@@ -4241,6 +4242,7 @@
     method public abstract void registerDexModule(@NonNull String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
     method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public void replacePreferredActivity(@NonNull android.content.IntentFilter, int, @NonNull java.util.List<android.content.ComponentName>, @NonNull android.content.ComponentName);
+    method @FlaggedApi("android.content.pm.uid_based_provider_lookup") @Nullable @RequiresPermission(android.Manifest.permission.RESOLVE_COMPONENT_FOR_UID) public android.content.pm.ProviderInfo resolveContentProviderForUid(@NonNull String, @NonNull android.content.pm.PackageManager.ComponentInfoFlags, int);
     method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public abstract void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull String);
     method public void sendDeviceCustomizationReadyBroadcast();
@@ -12676,27 +12678,21 @@
 package android.security.advancedprotection {
 
   @FlaggedApi("android.security.aapm_api") public final class AdvancedProtectionFeature implements android.os.Parcelable {
-    ctor public AdvancedProtectionFeature(@NonNull String);
+    ctor public AdvancedProtectionFeature(int);
     method public int describeContents();
-    method @NonNull public String getId();
+    method public int getId();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.security.advancedprotection.AdvancedProtectionFeature> CREATOR;
   }
 
   @FlaggedApi("android.security.aapm_api") public final class AdvancedProtectionManager {
-    method @NonNull public android.content.Intent createSupportIntent(@NonNull String, @Nullable String);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE) public java.util.List<android.security.advancedprotection.AdvancedProtectionFeature> getAdvancedProtectionFeatures();
     method @RequiresPermission(android.Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE) public void setAdvancedProtectionEnabled(boolean);
-    field @FlaggedApi("android.security.aapm_api") public static final String ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG = "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG";
-    field public static final String EXTRA_SUPPORT_DIALOG_FEATURE = "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE";
-    field public static final String EXTRA_SUPPORT_DIALOG_TYPE = "android.security.advancedprotection.extra.SUPPORT_DIALOG_TYPE";
-    field public static final String FEATURE_ID_DISALLOW_CELLULAR_2G = "android.security.advancedprotection.feature_disallow_2g";
-    field public static final String FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES = "android.security.advancedprotection.feature_disallow_install_unknown_sources";
-    field public static final String FEATURE_ID_DISALLOW_USB = "android.security.advancedprotection.feature_disallow_usb";
-    field public static final String FEATURE_ID_DISALLOW_WEP = "android.security.advancedprotection.feature_disallow_wep";
-    field public static final String FEATURE_ID_ENABLE_MTE = "android.security.advancedprotection.feature_enable_mte";
-    field public static final String SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION = "android.security.advancedprotection.type_blocked_interaction";
-    field public static final String SUPPORT_DIALOG_TYPE_DISABLED_SETTING = "android.security.advancedprotection.type_disabled_setting";
+    field public static final int FEATURE_ID_DISALLOW_CELLULAR_2G = 0; // 0x0
+    field public static final int FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES = 1; // 0x1
+    field public static final int FEATURE_ID_DISALLOW_USB = 2; // 0x2
+    field public static final int FEATURE_ID_DISALLOW_WEP = 3; // 0x3
+    field public static final int FEATURE_ID_ENABLE_MTE = 4; // 0x4
   }
 
 }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index abdfb535..999db18 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -485,6 +485,11 @@
      */
     public static final int OOM_ADJ_REASON_FOLLOW_UP = 23;
 
+    /**
+     * Oom Adj Reason: Update after oom adjuster configuration has changed.
+     */
+    public static final int OOM_ADJ_REASON_RECONFIGURATION = 24;
+
     @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = {
         OOM_ADJ_REASON_NONE,
         OOM_ADJ_REASON_ACTIVITY,
@@ -510,6 +515,7 @@
         OOM_ADJ_REASON_RESTRICTION_CHANGE,
         OOM_ADJ_REASON_COMPONENT_DISABLED,
         OOM_ADJ_REASON_FOLLOW_UP,
+        OOM_ADJ_REASON_RECONFIGURATION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OomAdjReason {}
diff --git a/core/java/android/app/AppOpsManager.aidl b/core/java/android/app/AppOpsManager.aidl
index b4dee2e..56ed290 100644
--- a/core/java/android/app/AppOpsManager.aidl
+++ b/core/java/android/app/AppOpsManager.aidl
@@ -19,6 +19,7 @@
 parcelable AppOpsManager.PackageOps;
 parcelable AppOpsManager.NoteOpEventProxyInfo;
 parcelable AppOpsManager.NoteOpEvent;
+parcelable AppOpsManager.NotedOp;
 parcelable AppOpsManager.OpFeatureEntry;
 parcelable AppOpsManager.OpEntry;
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1913812..53b4b54e 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -262,6 +262,23 @@
 
     private static final Object sLock = new Object();
 
+    // A map that records noted times for each op.
+    private static ArrayMap<NotedOp, Integer> sPendingNotedOps = new ArrayMap<>();
+    private static HandlerThread sHandlerThread;
+    private static final int NOTE_OP_BATCHING_DELAY_MILLIS = 1000;
+
+    private boolean isNoteOpBatchingSupported() {
+        // If noteOp is called from system server no IPC is made, hence we don't need batching.
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            return false;
+        }
+        return Flags.noteOpBatchingEnabled();
+    }
+
+    private static final Object sBatchedNoteOpLock = new Object();
+    @GuardedBy("sBatchedNoteOpLock")
+    private static boolean sIsBatchedNoteOpCallScheduled = false;
+
     /** Current {@link OnOpNotedCallback}. Change via {@link #setOnOpNotedCallback} */
     @GuardedBy("sLock")
     private static @Nullable OnOpNotedCallback sOnOpNotedCallback;
@@ -7466,6 +7483,141 @@
     }
 
     /**
+     * A NotedOp is an app op grouped in noteOp API and sent to the system server in a batch
+     *
+     * @hide
+     */
+    public static final class NotedOp implements Parcelable {
+        private final @IntRange(from = 0, to = _NUM_OP - 1) int mOp;
+        private final @IntRange(from = 0) int mUid;
+        private final @Nullable String mPackageName;
+        private final @Nullable String mAttributionTag;
+        private final int mVirtualDeviceId;
+        private final @Nullable String mMessage;
+        private final boolean mShouldCollectAsyncNotedOp;
+        private final boolean mShouldCollectMessage;
+
+        public NotedOp(int op, int uid, @Nullable String packageName,
+                @Nullable String attributionTag, int virtualDeviceId, @Nullable String message,
+                boolean shouldCollectAsyncNotedOp, boolean shouldCollectMessage) {
+            mOp = op;
+            mUid = uid;
+            mPackageName = packageName;
+            mAttributionTag = attributionTag;
+            mVirtualDeviceId = virtualDeviceId;
+            mMessage = message;
+            mShouldCollectAsyncNotedOp = shouldCollectAsyncNotedOp;
+            mShouldCollectMessage = shouldCollectMessage;
+        }
+
+        NotedOp(Parcel source) {
+            mOp = source.readInt();
+            mUid = source.readInt();
+            mPackageName = source.readString();
+            mAttributionTag = source.readString();
+            mVirtualDeviceId = source.readInt();
+            mMessage = source.readString();
+            mShouldCollectAsyncNotedOp = source.readBoolean();
+            mShouldCollectMessage = source.readBoolean();
+        }
+
+        public int getOp() {
+            return mOp;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public @Nullable String getPackageName() {
+            return mPackageName;
+        }
+
+        public @Nullable String getAttributionTag() {
+            return mAttributionTag;
+        }
+
+        public int getVirtualDeviceId() {
+            return mVirtualDeviceId;
+        }
+
+        public @Nullable String getMessage() {
+            return mMessage;
+        }
+
+        public boolean getShouldCollectAsyncNotedOp() {
+            return mShouldCollectAsyncNotedOp;
+        }
+
+        public boolean getShouldCollectMessage() {
+            return mShouldCollectMessage;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mOp);
+            dest.writeInt(mUid);
+            dest.writeString(mPackageName);
+            dest.writeString(mAttributionTag);
+            dest.writeInt(mVirtualDeviceId);
+            dest.writeString(mMessage);
+            dest.writeBoolean(mShouldCollectAsyncNotedOp);
+            dest.writeBoolean(mShouldCollectMessage);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            NotedOp that = (NotedOp) o;
+            return mOp == that.mOp
+                    && mUid == that.mUid
+                    && Objects.equals(mPackageName, that.mPackageName)
+                    && Objects.equals(mAttributionTag, that.mAttributionTag)
+                    && mVirtualDeviceId == that.mVirtualDeviceId
+                    && Objects.equals(mMessage, that.mMessage)
+                    && Objects.equals(mShouldCollectAsyncNotedOp, that.mShouldCollectAsyncNotedOp)
+                    && Objects.equals(mShouldCollectMessage, that.mShouldCollectMessage);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mOp, mUid, mPackageName, mAttributionTag, mVirtualDeviceId,
+                    mMessage, mShouldCollectAsyncNotedOp, mShouldCollectMessage);
+        }
+
+        @Override
+        public String toString() {
+            return "NotedOp{"
+                    + "mOp=" + mOp
+                    + ", mUid=" + mUid
+                    + ", mPackageName=" + mPackageName
+                    + ", mAttributionTag=" + mAttributionTag
+                    + ", mVirtualDeviceId=" + mVirtualDeviceId
+                    + ", mMessage=" + mMessage
+                    + ", mShouldCollectAsyncNotedOp=" + mShouldCollectAsyncNotedOp
+                    + ", mShouldCollectMessage=" + mShouldCollectMessage
+                    + "}";
+        }
+
+        public static final @NonNull Creator<NotedOp> CREATOR =
+                new Creator<>() {
+                    @Override public NotedOp createFromParcel(Parcel source) {
+                        return new NotedOp(source);
+                    }
+
+                    @Override public NotedOp[] newArray(int size) {
+                        return new NotedOp[size];
+                    }
+                };
+    }
+
+    /**
      * Computes the sum of the counts for the given flags in between the begin and
      * end UID states.
      *
@@ -9301,6 +9453,65 @@
                 message);
     }
 
+    /**
+     * Create a new NotedOp object to represent the note operation. If the note operation is
+     * a duplicate in the buffer, put it in a batch for an async binder call to the system server.
+     *
+     * @return whether this note operation is a duplicate in the buffer. If it's the
+     * first, the noteOp is not batched, the caller should manually call noteOperation.
+     */
+    private boolean batchDuplicateNoteOps(int op, int uid, @Nullable String packageName,
+            @Nullable String attributionTag, int virtualDeviceId, @Nullable String message,
+            boolean collectAsync, boolean shouldCollectMessage) {
+        synchronized (sBatchedNoteOpLock) {
+            NotedOp notedOp = new NotedOp(op, uid, packageName, attributionTag,
+                    virtualDeviceId, message, collectAsync, shouldCollectMessage);
+
+            // Batch same noteOp calls and send them with their counters to the system
+            // service asynchronously. The time window for batching is specified in
+            // NOTE_OP_BATCHING_DELAY_MILLIS. Always allow the first noteOp call to go
+            // through binder API. Accumulate subsequent same noteOp calls during the
+            // time window in sPendingNotedOps.
+            boolean isDuplicated = sPendingNotedOps.containsKey(notedOp);
+            if (!isDuplicated) {
+                sPendingNotedOps.put(notedOp, 0);
+            } else {
+                sPendingNotedOps.merge(notedOp, 1, Integer::sum);
+            }
+
+            if (!sIsBatchedNoteOpCallScheduled) {
+                if (sHandlerThread == null) {
+                    sHandlerThread = new HandlerThread("AppOpsManagerNoteOpBatching");
+                    sHandlerThread.start();
+                }
+
+                sHandlerThread.getThreadHandler().postDelayed(() -> {
+                    ArrayMap<NotedOp, Integer> pendingNotedOpsCopy;
+                    synchronized(sBatchedNoteOpLock) {
+                        sIsBatchedNoteOpCallScheduled = false;
+                        pendingNotedOpsCopy = sPendingNotedOps;
+                        sPendingNotedOps = new ArrayMap<>();
+                    }
+                    for (int i = pendingNotedOpsCopy.size() - 1; i >= 0; i--) {
+                        if (pendingNotedOpsCopy.valueAt(i) == 0) {
+                            pendingNotedOpsCopy.removeAt(i);
+                        }
+                    }
+                    if (!pendingNotedOpsCopy.isEmpty()) {
+                        try {
+                            mService.noteOperationsInBatch(pendingNotedOpsCopy);
+                        } catch (RemoteException e) {
+                            throw e.rethrowFromSystemServer();
+                        }
+                    }
+                }, NOTE_OP_BATCHING_DELAY_MILLIS);
+
+                sIsBatchedNoteOpCallScheduled = true;
+            }
+            return isDuplicated;
+        }
+    }
+
     private int noteOpNoThrow(int op, int uid, @Nullable String packageName,
             @Nullable String attributionTag, int virtualDeviceId, @Nullable String message) {
         try {
@@ -9315,15 +9526,34 @@
                 }
             }
 
-            SyncNotedAppOp syncOp;
-            if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
-                syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
-                        collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
-            } else {
-                syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag,
-                    virtualDeviceId, collectionMode == COLLECT_ASYNC, message,
-                    shouldCollectMessage);
+            SyncNotedAppOp syncOp = null;
+            boolean isNoteOpDuplicated = false;
+            if (isNoteOpBatchingSupported()) {
+                int mode = sAppOpModeCache.query(
+                        new AppOpModeQuery(op, uid, packageName, virtualDeviceId, attributionTag,
+                                "noteOpNoThrow"));
+                // For FOREGROUND mode, we still need to make a binder call to the system service
+                // to translate it to ALLOWED or IGNORED. So no batching is needed.
+                if (mode != MODE_FOREGROUND) {
+                    isNoteOpDuplicated = batchDuplicateNoteOps(op, uid, packageName, attributionTag,
+                            virtualDeviceId, message,
+                            collectionMode == COLLECT_ASYNC, shouldCollectMessage);
+
+                    syncOp = new SyncNotedAppOp(mode, op, attributionTag, packageName);
+                }
             }
+
+            if (!isNoteOpDuplicated) {
+                if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+                    syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
+                            collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
+                } else {
+                    syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag,
+                            virtualDeviceId, collectionMode == COLLECT_ASYNC, message,
+                            shouldCollectMessage);
+                }
+            }
+
             if (syncOp.getOpMode() == MODE_ALLOWED) {
                 if (collectionMode == COLLECT_SELF) {
                     collectNotedOpForSelf(syncOp);
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index b21defb..8b7ea0f 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -29,7 +29,7 @@
 import com.android.internal.util.function.DodecFunction;
 import com.android.internal.util.function.HexConsumer;
 import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.NonaFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.UndecFunction;
 
@@ -86,9 +86,9 @@
          */
         SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
                 @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
-                @Nullable String message, boolean shouldCollectMessage,
-                @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String,
-                        Boolean, SyncNotedAppOp> superImpl);
+                @Nullable String message, boolean shouldCollectMessage, int notedCount,
+                @NonNull NonaFunction<Integer, Integer, String, String, Integer, Boolean, String,
+                        Boolean, Integer, SyncNotedAppOp> superImpl);
 
         /**
          * Allows overriding note proxy operation behavior.
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index da33847..2dead56 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1751,6 +1751,19 @@
         }
     }
 
+    /** @hide **/
+    @Override
+    public ProviderInfo resolveContentProviderForUid(@NonNull String authority,
+            ComponentInfoFlags flags, int callingUid) {
+        try {
+            return mPM.resolveContentProviderForUid(authority,
+                updateFlagsForComponent(flags.getValue(), getUserId(), null), getUserId(),
+                callingUid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @Override
     public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {
         return queryContentProviders(processName, uid, ComponentInfoFlags.of(flags));
diff --git a/core/java/android/app/BackgroundStartPrivileges.java b/core/java/android/app/BackgroundStartPrivileges.java
index 20278ea..adea0a8 100644
--- a/core/java/android/app/BackgroundStartPrivileges.java
+++ b/core/java/android/app/BackgroundStartPrivileges.java
@@ -23,12 +23,13 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Privileges granted to a Process that allows it to execute starts from the background.
  * @hide
  */
-public class BackgroundStartPrivileges {
+public final class BackgroundStartPrivileges {
     /** No privileges. */
     public static final BackgroundStartPrivileges NONE = new BackgroundStartPrivileges(
             false, false, null);
@@ -190,4 +191,22 @@
                 + ", originatingToken=" + mOriginatingToken
                 + ']';
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        BackgroundStartPrivileges that = (BackgroundStartPrivileges) o;
+        return mAllowsBackgroundActivityStarts == that.mAllowsBackgroundActivityStarts
+                && mAllowsBackgroundForegroundServiceStarts
+                == that.mAllowsBackgroundForegroundServiceStarts
+                && Objects.equals(mOriginatingToken, that.mOriginatingToken);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAllowsBackgroundActivityStarts,
+                mAllowsBackgroundForegroundServiceStarts,
+                mOriginatingToken);
+    }
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 9f898b8..e6ddbf4 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -196,6 +196,21 @@
     ProviderInfo resolveContentProvider(String name, long flags, int userId);
 
     /**
+     * Resolve content providers with a given authority, for a specific
+     * callingUid.
+     *
+     * @param authority Authority of the content provider
+     * @param flags Additional option flags to modify the data returned.
+     * @param userId Current user ID
+     * @param callingUid UID of the caller who's access to the content provider
+              is to be checked
+     *
+     *  @return ProviderInfo of the resolved content provider. May return null
+    */
+    ProviderInfo resolveContentProviderForUid(String authority, long flags,
+      int userId, int callingUid);
+
+    /**
      * Retrieve sync information for all content providers.
      *
      * @param outNames Filled in with a list of the root names of the content
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 438a21b..c16582f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -8349,6 +8349,25 @@
     }
 
     /**
+     * Resolve content providers with a given authority, for a specific callingUid.
+     * @param authority Authority of the content provider
+     * @param flags Additional option flags to modify the data returned.
+     * @param callingUid UID of the caller who's access to the content provider is to be checked
+
+     * @return ProviderInfo of the resolved content provider.
+     * @hide
+     */
+    @Nullable
+    @FlaggedApi(android.content.pm.Flags.FLAG_UID_BASED_PROVIDER_LOOKUP)
+    @RequiresPermission(Manifest.permission.RESOLVE_COMPONENT_FOR_UID)
+    @SystemApi
+    public ProviderInfo resolveContentProviderForUid(@NonNull String authority,
+        @NonNull ComponentInfoFlags flags, int callingUid) {
+        throw new UnsupportedOperationException(
+            "resolveContentProviderForUid not implemented in subclass");
+    }
+
+    /**
      * Retrieve content provider information.
      * <p>
      * <em>Note: unlike most other methods, an empty result set is indicated
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 0d219a9..7bba06c 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -152,7 +152,7 @@
     name: "cache_sdk_system_features"
     namespace: "system_performance"
     description: "Feature flag to enable optimized cache for SDK-defined system feature lookups."
-    bug: "375000483"
+    bug: "326623529"
 }
 
 flag {
@@ -375,3 +375,11 @@
     description: "Feature flag to remove the consumption of the hidden module status (ModuleInfo#IsHidden) in the Android source tree."
     bug: "363952383"
 }
+
+flag {
+    name: "uid_based_provider_lookup"
+    is_exported: true
+    namespace: "package_manager_service"
+    bug: "334024639"
+    description: "Feature flag to check whether a given UID can access a content provider"
+}
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 4fbdf7f..d88a9d4 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -56,15 +56,18 @@
     void stopWifiDisplayScan();
 
     // Requires CONFIGURE_WIFI_DISPLAY permission.
+    @EnforcePermission("CONFIGURE_WIFI_DISPLAY")
     void connectWifiDisplay(String address);
 
     // No permissions required.
     void disconnectWifiDisplay();
 
     // Requires CONFIGURE_WIFI_DISPLAY permission.
+    @EnforcePermission("CONFIGURE_WIFI_DISPLAY")
     void renameWifiDisplay(String address, String alias);
 
     // Requires CONFIGURE_WIFI_DISPLAY permission.
+    @EnforcePermission("CONFIGURE_WIFI_DISPLAY")
     void forgetWifiDisplay(String address);
 
     // Requires CONFIGURE_WIFI_DISPLAY permission.
@@ -169,6 +172,7 @@
     void setBrightness(int displayId, float brightness);
 
     // Retrieves the display brightness.
+    @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
     float getBrightness(int displayId);
 
     // Temporarily sets the auto brightness adjustment factor.
@@ -196,8 +200,7 @@
 
     // Sets the HDR conversion mode for a device.
     // Requires MODIFY_HDR_CONVERSION_MODE permission.
-    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
-                + ".permission.MODIFY_HDR_CONVERSION_MODE)")
+    @EnforcePermission("MODIFY_HDR_CONVERSION_MODE")
     void setHdrConversionMode(in HdrConversionMode hdrConversionMode);
     HdrConversionMode getHdrConversionModeSetting();
     HdrConversionMode getHdrConversionMode();
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 01222cd..18d2afb 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -687,12 +687,18 @@
         return removeFrozenStateChangeCallbackNative(wrappedCallback);
     }
 
+    public static boolean isFrozenStateChangeCallbackSupported() {
+        return isFrozenStateChangeCallbackSupportedNative();
+    }
+
     private native void addFrozenStateChangeCallbackNative(FrozenStateChangeCallback callback)
             throws RemoteException;
 
     private native boolean removeFrozenStateChangeCallbackNative(
             FrozenStateChangeCallback callback);
 
+    private static native boolean isFrozenStateChangeCallbackSupportedNative();
+
     /**
      * Perform a dump on the remote object
      *
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index f3bb514..727dcba 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -4,6 +4,8 @@
 
 # PowerManager
 per-file IPowerManager.aidl = file:/services/core/java/com/android/server/power/OWNERS
+per-file IScreenTimeoutPolicyListener.aidl = file:/services/core/java/com/android/server/power/OWNERS
+per-file IWakeLockCallback.aidl = file:/services/core/java/com/android/server/power/OWNERS
 per-file PowerManager.java = file:/services/core/java/com/android/server/power/OWNERS
 per-file PowerManagerInternal.java = file:/services/core/java/com/android/server/power/OWNERS
 
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 132805d..507bcb8 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -940,10 +940,10 @@
 
     /**
      * Specifies if a user is disallowed from resetting network settings
-     * from Settings. This can only be set by device owners and profile owners on the primary user.
+     * from Settings. This can only be set by device owners and profile owners on the main user.
      * The default value is <code>false</code>.
-     * <p>This restriction has no effect on secondary users and managed profiles since only the
-     * primary user can reset the network settings of the device.
+     * <p>This restriction has no effect on non-Admin users since they cannot reset the network
+     * settings of the device.
      *
      * <p>Holders of the permission
      * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
@@ -1077,11 +1077,11 @@
     /**
      * Specifies if a user is disallowed from configuring cell broadcasts.
      *
-     * <p>This restriction can only be set by a device owner, a profile owner on the primary
+     * <p>This restriction can only be set by a device owner, a profile owner on the main
      * user or a profile owner of an organization-owned managed profile on the parent profile.
      * When it is set by a device owner, it applies globally. When it is set by a profile owner
-     * on the primary user or by a profile owner of an organization-owned managed profile on
-     * the parent profile, it disables the primary user from configuring cell broadcasts.
+     * on the main user or by a profile owner of an organization-owned managed profile on
+     * the parent profile, it disables the user from configuring cell broadcasts.
      *
      * <p>Holders of the permission
      * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
@@ -1089,8 +1089,8 @@
      *
      * <p>The default value is <code>false</code>.
      *
-     * <p>This restriction has no effect on secondary users and managed profiles since only the
-     * primary user can configure cell broadcasts.
+     * <p>This restriction has no effect on non-Admin users since they cannot configure cell
+     * broadcasts.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
@@ -1103,11 +1103,11 @@
     /**
      * Specifies if a user is disallowed from configuring mobile networks.
      *
-     * <p>This restriction can only be set by a device owner, a profile owner on the primary
+     * <p>This restriction can only be set by a device owner, a profile owner on the main
      * user or a profile owner of an organization-owned managed profile on the parent profile.
      * When it is set by a device owner, it applies globally. When it is set by a profile owner
-     * on the primary user or by a profile owner of an organization-owned managed profile on
-     * the parent profile, it disables the primary user from configuring mobile networks.
+     * on the main user or by a profile owner of an organization-owned managed profile on
+     * the parent profile, it disables the user from configuring mobile networks.
      *
      * <p>Holders of the permission
      * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
@@ -1115,8 +1115,8 @@
      *
      * <p>The default value is <code>false</code>.
      *
-     * <p>This restriction has no effect on secondary users and managed profiles since only the
-     * primary user can configure mobile networks.
+     * <p>This restriction has no effect on non-Admin users since they cannot configure mobile
+     * networks.
      *
      * <p>Key for user restrictions.
      * <p>Type: Boolean
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionFeature.java b/core/java/android/security/advancedprotection/AdvancedProtectionFeature.java
index a086bf7..d476d96 100644
--- a/core/java/android/security/advancedprotection/AdvancedProtectionFeature.java
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionFeature.java
@@ -30,26 +30,25 @@
 @FlaggedApi(Flags.FLAG_AAPM_API)
 @SystemApi
 public final class AdvancedProtectionFeature implements Parcelable {
-    private final String mId;
+    private final int mId;
 
     /**
      * Create an object identifying an Advanced Protection feature for AdvancedProtectionManager
-     * @param id A unique ID to identify this feature. It is used by Settings screens to display
-     *           information about this feature.
+     * @param id Feature identifier. It is used by Settings screens to display information about
+     *           this feature.
      */
-    public AdvancedProtectionFeature(@NonNull String id) {
+    public AdvancedProtectionFeature(@AdvancedProtectionManager.FeatureId int id) {
         mId = id;
     }
 
     private AdvancedProtectionFeature(Parcel in) {
-        mId = in.readString8();
+        mId = in.readInt();
     }
 
     /**
      * @return the unique ID representing this feature
      */
-    @NonNull
-    public String getId() {
+    public int getId() {
         return mId;
     }
 
@@ -60,7 +59,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString8(mId);
+        dest.writeInt(mId);
     }
 
     @NonNull
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
index 59628e8..ea01fc9 100644
--- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
@@ -24,17 +24,18 @@
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.SdkConstant;
-import android.annotation.StringDef;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
+import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.RemoteException;
+import android.os.UserManager;
 import android.security.Flags;
 import android.util.Log;
 
@@ -59,54 +60,57 @@
     private static final String TAG = "AdvancedProtectionMgr";
 
     /**
-     * Advanced Protection's identifier for setting policies or restrictions in DevicePolicyManager.
+     * Advanced Protection's identifier for setting policies or restrictions in
+     * {@link DevicePolicyManager}.
      *
      * @hide */
     public static final String ADVANCED_PROTECTION_SYSTEM_ENTITY =
             "android.security.advancedprotection";
 
     /**
-     * Feature identifier for disallowing 2G.
+     * Feature identifier for disallowing connections to 2G networks.
      *
+     * @see UserManager#DISALLOW_CELLULAR_2G
      * @hide */
     @SystemApi
-    public static final String FEATURE_ID_DISALLOW_CELLULAR_2G =
-            "android.security.advancedprotection.feature_disallow_2g";
+    public static final int FEATURE_ID_DISALLOW_CELLULAR_2G = 0;
 
     /**
-     * Feature identifier for disallowing install of unknown sources.
+     * Feature identifier for disallowing installs of apps from unknown sources.
      *
+     * @see UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
      * @hide */
     @SystemApi
-    public static final String FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES =
-            "android.security.advancedprotection.feature_disallow_install_unknown_sources";
+    public static final int FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES = 1;
 
     /**
-     * Feature identifier for disallowing USB.
+     * Feature identifier for disallowing USB connections.
      *
      * @hide */
     @SystemApi
-    public static final String FEATURE_ID_DISALLOW_USB =
-            "android.security.advancedprotection.feature_disallow_usb";
+    public static final int FEATURE_ID_DISALLOW_USB = 2;
 
     /**
-     * Feature identifier for disallowing WEP.
+     * Feature identifier for disallowing connections to Wi-Fi Wired Equivalent Privacy (WEP)
+     * networks.
      *
+     * @see WifiManager#isWepSupported()
      * @hide */
     @SystemApi
-    public static final String FEATURE_ID_DISALLOW_WEP =
-            "android.security.advancedprotection.feature_disallow_wep";
+    public static final int FEATURE_ID_DISALLOW_WEP = 3;
 
     /**
-     * Feature identifier for enabling MTE.
+     * Feature identifier for enabling the Memory Tagging Extension (MTE). MTE is a CPU extension
+     * that allows to protect against certain classes of security problems at a small runtime
+     * performance cost overhead.
      *
+     * @see DevicePolicyManager#setMtePolicy(int)
      * @hide */
     @SystemApi
-    public static final String FEATURE_ID_ENABLE_MTE =
-            "android.security.advancedprotection.feature_enable_mte";
+    public static final int FEATURE_ID_ENABLE_MTE = 4;
 
     /** @hide */
-    @StringDef(prefix = { "FEATURE_ID_" }, value = {
+    @IntDef(prefix = { "FEATURE_ID_" }, value = {
             FEATURE_ID_DISALLOW_CELLULAR_2G,
             FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES,
             FEATURE_ID_DISALLOW_USB,
@@ -116,7 +120,7 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface FeatureId {}
 
-    private static final Set<String> ALL_FEATURE_IDS = Set.of(
+    private static final Set<Integer> ALL_FEATURE_IDS = Set.of(
             FEATURE_ID_DISALLOW_CELLULAR_2G,
             FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES,
             FEATURE_ID_DISALLOW_USB,
@@ -135,9 +139,6 @@
      * Output: Nothing.
      *
      * @hide */
-    @SystemApi
-    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
-    @FlaggedApi(android.security.Flags.FLAG_AAPM_API)
     public static final String ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG =
             "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG";
 
@@ -147,7 +148,6 @@
      *
      * @hide */
     @FeatureId
-    @SystemApi
     public static final String EXTRA_SUPPORT_DIALOG_FEATURE =
             "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE";
 
@@ -157,37 +157,41 @@
      *
      * @hide */
     @SupportDialogType
-    @SystemApi
     public static final String EXTRA_SUPPORT_DIALOG_TYPE =
             "android.security.advancedprotection.extra.SUPPORT_DIALOG_TYPE";
 
     /**
+     * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating an unknown action was blocked by
+     * advanced protection, hence the support dialog should display a default explanation.
+     *
+     * @hide */
+    public static final int SUPPORT_DIALOG_TYPE_UNKNOWN = 0;
+
+    /**
      * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user performed an action that was
      * blocked by advanced protection.
      *
      * @hide */
-    @SystemApi
-    public static final String SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION =
-            "android.security.advancedprotection.type_blocked_interaction";
+    public static final int SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION = 1;
 
     /**
      * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user pressed on a setting toggle
      * that was disabled by advanced protection.
      *
      * @hide */
-    @SystemApi
-    public static final String SUPPORT_DIALOG_TYPE_DISABLED_SETTING =
-            "android.security.advancedprotection.type_disabled_setting";
+    public static final int SUPPORT_DIALOG_TYPE_DISABLED_SETTING = 2;
 
     /** @hide */
-    @StringDef(prefix = { "SUPPORT_DIALOG_TYPE_" }, value = {
+    @IntDef(prefix = { "SUPPORT_DIALOG_TYPE_" }, value = {
+            SUPPORT_DIALOG_TYPE_UNKNOWN,
             SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION,
             SUPPORT_DIALOG_TYPE_DISABLED_SETTING,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SupportDialogType {}
 
-    private static final Set<String> ALL_SUPPORT_DIALOG_TYPES = Set.of(
+    private static final Set<Integer> ALL_SUPPORT_DIALOG_TYPES = Set.of(
+            SUPPORT_DIALOG_TYPE_UNKNOWN,
             SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION,
             SUPPORT_DIALOG_TYPE_DISABLED_SETTING);
 
@@ -324,15 +328,13 @@
      *                disabled by advanced protection.
      * @hide
      */
-    @SystemApi
-    public @NonNull Intent createSupportIntent(@NonNull @FeatureId String featureId,
-            @Nullable @SupportDialogType String type) {
-        Objects.requireNonNull(featureId);
+    public static @NonNull Intent createSupportIntent(@FeatureId int featureId,
+            @SupportDialogType int type) {
         if (!ALL_FEATURE_IDS.contains(featureId)) {
             throw new IllegalArgumentException(featureId + " is not a valid feature ID. See"
                     + " FEATURE_ID_* APIs.");
         }
-        if (type != null && !ALL_SUPPORT_DIALOG_TYPES.contains(type)) {
+        if (!ALL_SUPPORT_DIALOG_TYPES.contains(type)) {
             throw new IllegalArgumentException(type + " is not a valid type. See"
                     + " SUPPORT_DIALOG_TYPE_* APIs.");
         }
@@ -340,21 +342,19 @@
         Intent intent = new Intent(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG);
         intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
         intent.putExtra(EXTRA_SUPPORT_DIALOG_FEATURE, featureId);
-        if (type != null) {
-            intent.putExtra(EXTRA_SUPPORT_DIALOG_TYPE, type);
-        }
+        intent.putExtra(EXTRA_SUPPORT_DIALOG_TYPE, type);
         return intent;
     }
 
     /** @hide */
-    public @NonNull Intent createSupportIntentForPolicyIdentifierOrRestriction(
-            @NonNull String identifier, @Nullable @SupportDialogType String type) {
+    public static @NonNull Intent createSupportIntentForPolicyIdentifierOrRestriction(
+            @NonNull String identifier, @SupportDialogType int type) {
         Objects.requireNonNull(identifier);
-        if (type != null && !ALL_SUPPORT_DIALOG_TYPES.contains(type)) {
+        if (!ALL_SUPPORT_DIALOG_TYPES.contains(type)) {
             throw new IllegalArgumentException(type + " is not a valid type. See"
                     + " SUPPORT_DIALOG_TYPE_* APIs.");
         }
-        final String featureId;
+        final int featureId;
         if (DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY.equals(identifier)) {
             featureId = FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES;
         } else if (DISALLOW_CELLULAR_2G.equals(identifier)) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8f8bfe2..d88b6d6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -34199,7 +34199,8 @@
                 && viewRootImpl.shouldCheckFrameRateCategory()
                 && parent instanceof View
                 && ((View) parent).getFrameContentVelocity() <= 0
-                && !isInputMethodWindowType) {
+                && !isInputMethodWindowType
+                && viewRootImpl.getFrameRateCompatibility() != FRAME_RATE_COMPATIBILITY_AT_LEAST) {
 
             return FRAME_RATE_CATEGORY_HIGH_HINT | FRAME_RATE_CATEGORY_REASON_BOOST;
         }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f82e5f9..d5f471e 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -938,27 +938,6 @@
             synchronized (mH) {
                 if (mCurRootView == viewRootImpl) {
                     mCurRootViewWindowFocused = false;
-
-                    if (Flags.refactorInsetsController() && mCurRootView != null) {
-                        final int softInputMode = mCurRootView.mWindowAttributes.softInputMode;
-                        final int state =
-                                softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
-                        if (state == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
-                            // when losing focus (e.g., by going to another window), we reset the
-                            // requestedVisibleTypes of WindowInsetsController by hiding the IME
-                            final var statsToken = ImeTracker.forLogging().onStart(
-                                    ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_CLIENT,
-                                    SoftInputShowHideReason.HIDE_WINDOW_LOST_FOCUS,
-                                    false /* fromUser */);
-                            if (DEBUG) {
-                                Log.d(TAG, "onWindowLostFocus, hiding IME because "
-                                        + "of STATE_ALWAYS_HIDDEN");
-                            }
-                            mCurRootView.getInsetsController().hide(WindowInsets.Type.ime(),
-                                    false /* fromIme */, statsToken);
-                        }
-                    }
-
                     clearCurRootViewIfNeeded();
                 }
             }
@@ -1012,6 +991,26 @@
         @GuardedBy("mH")
         private void setCurrentRootViewLocked(ViewRootImpl rootView) {
             final boolean wasEmpty = mCurRootView == null;
+            if (Flags.refactorInsetsController() && !wasEmpty && mCurRootView != rootView) {
+                final int softInputMode = mCurRootView.mWindowAttributes.softInputMode;
+                final int state =
+                        softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+                if (state == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
+                    // when losing input focus (e.g., by going to another window), we reset the
+                    // requestedVisibleTypes of WindowInsetsController by hiding the IME
+                    final var statsToken = ImeTracker.forLogging().onStart(
+                            ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_CLIENT,
+                            SoftInputShowHideReason.HIDE_WINDOW_LOST_FOCUS,
+                            false /* fromUser */);
+                    if (DEBUG) {
+                        Log.d(TAG, "setCurrentRootViewLocked, hiding IME because "
+                                + "of STATE_ALWAYS_HIDDEN");
+                    }
+                    mCurRootView.getInsetsController().hide(WindowInsets.Type.ime(),
+                            false /* fromIme */, statsToken);
+                }
+            }
+
             mImeDispatcher.switchRootView(mCurRootView, rootView);
             mCurRootView = rootView;
             if (wasEmpty && mCurRootView != null) {
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 0d04961..a42759e 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -2,10 +2,10 @@
 container: "system"
 
 flag {
-  name: "disable_thin_letterboxing_policy"
+  name: "ignore_aspect_ratio_restrictions_for_resizeable_freeform_activities"
   namespace: "large_screen_experiences_app_compat"
-  description: "Whether reachability is disabled in case of thin letterboxing"
-  bug: "341027847"
+  description: "If a resizeable activity enters freeform mode, ignore all aspect ratio constraints."
+  bug: "381866902"
   metadata {
     purpose: PURPOSE_BUGFIX
   }
@@ -73,16 +73,6 @@
 }
 
 flag {
-  name: "immersive_app_repositioning"
-  namespace: "large_screen_experiences_app_compat"
-  description: "Fix immersive apps changing size when repositioning"
-  bug: "334076352"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "camera_compat_for_freeform"
   namespace: "large_screen_experiences_app_compat"
   description: "Whether to apply Camera Compat treatment to fixed-orientation apps in freeform windowing mode"
@@ -155,4 +145,14 @@
   description: "Whether the API for forcing apps to be universal resizable on virtual display is available"
   bug: "372848702"
   is_exported: true
+}
+
+flag {
+  name: "release_user_aspect_ratio_wm"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether to release UserAspectRatioSettingsWindowManager when button is hidden"
+  bug: "385049711"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 9d11d14..c1ed512 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -368,17 +368,6 @@
 }
 
 flag {
-  name: "disallow_app_progress_embedded_window"
-  namespace: "windowing_frontend"
-  description: "Pilfer pointers when app transfer input gesture to embedded window."
-  bug: "365504126"
-  is_fixed_read_only: true
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
     name: "predictive_back_system_override_callback"
     namespace: "windowing_frontend"
     description: "Provide pre-make predictive back API extension"
@@ -430,4 +419,15 @@
   description: "Support insets definition and calculation relative to task bounds."
   bug: "277292497"
   is_fixed_read_only: true
+}
+
+flag {
+    name: "exclude_drawing_app_theme_snapshot_from_lock"
+    namespace: "windowing_frontend"
+    description: "Do not hold wm lock when drawing app theme snapshot."
+    is_fixed_read_only: true
+    bug: "373502791"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 2cfc680..f01aa80 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -163,4 +163,5 @@
     void finishOperationForDevice(IBinder clientId, int code, int uid, String packageName,
             @nullable String attributionTag, int virtualDeviceId);
    List<AppOpsManager.PackageOps> getPackagesForOpsForDevice(in int[] ops, String persistentDeviceId);
+   oneway void noteOperationsInBatch(in Map batchedNoteOps);
 }
diff --git a/core/java/com/android/internal/app/ProcessMap.java b/core/java/com/android/internal/app/ProcessMap.java
index 542b6d0..b4945e7 100644
--- a/core/java/com/android/internal/app/ProcessMap.java
+++ b/core/java/com/android/internal/app/ProcessMap.java
@@ -28,6 +28,11 @@
         if (uids == null) return null;
         return uids.get(uid);
     }
+
+    public SparseArray<E> get(String name) {
+        SparseArray<E> uids = mMap.get(name);
+        return uids;
+    }
     
     public E put(String name, int uid, E value) {
         SparseArray<E> uids = mMap.get(name);
diff --git a/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS b/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
index 7755000..3ed902f 100644
--- a/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
+++ b/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
@@ -1,3 +1,5 @@
 # TODO(b/274465475): Migrate LatencyTracker testing to its own module
 marcinoc@google.com
 ilkos@google.com
+jjaggi@google.com
+nicomazz@google.com
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 5acdf32..027113a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -482,22 +482,11 @@
                 "libbinder",
                 "libhidlbase", // libhwbinder is in here
             ],
-            version_script: "platform/linux/libandroid_runtime_export.txt",
-        },
-        darwin: {
-            host_ldlibs: [
-                "-framework AppKit",
-            ],
-            dist: {
-                targets: ["layoutlib_jni"],
-                dir: "layoutlib_native/darwin",
-            },
-            exported_symbols_list: "platform/darwin/libandroid_runtime_export.exp",
         },
         linux_glibc_x86_64: {
             ldflags: ["-static-libgcc"],
             dist: {
-                targets: ["layoutlib_jni"],
+                targets: ["layoutlib"],
                 dir: "layoutlib_native/linux",
                 tag: "stripped_all",
             },
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 8003bb7..639f5bf 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -1706,6 +1706,10 @@
     return res;
 }
 
+static jboolean android_os_BinderProxy_frozenStateChangeCallbackSupported(JNIEnv*, jclass*) {
+    return ProcessState::isDriverFeatureEnabled(ProcessState::DriverFeature::FREEZE_NOTIFICATION);
+}
+
 static void BinderProxy_destroy(void* rawNativeData)
 {
     BinderProxyNativeData * nativeData = (BinderProxyNativeData *) rawNativeData;
@@ -1750,6 +1754,8 @@
         "(Landroid/os/IBinder$FrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback},
     {"removeFrozenStateChangeCallbackNative",
         "(Landroid/os/IBinder$FrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback},
+    {"isFrozenStateChangeCallbackSupportedNative",
+        "()Z", (void*)android_os_BinderProxy_frozenStateChangeCallbackSupported},
     {"getNativeFinalizer",  "()J", (void*)android_os_BinderProxy_getNativeFinalizer},
     {"getExtension",        "()Landroid/os/IBinder;", (void*)android_os_BinderProxy_getExtension},
 };
diff --git a/core/jni/platform/darwin/libandroid_runtime_export.exp b/core/jni/platform/darwin/libandroid_runtime_export.exp
deleted file mode 100644
index 00a7585..0000000
--- a/core/jni/platform/darwin/libandroid_runtime_export.exp
+++ /dev/null
@@ -1,38 +0,0 @@
-#
-# 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.
-#
-
-# symbols needed for the JNI operations
-_JNI_OnLoad
-_ANativeWindow*
-
-# symbols needed to link with layoutlib_jni
-___android_log*
-__ZNK7android7RefBase*
-__ZN7android4base9SetLogger*
-__ZN7android4base10SetAborter*
-__ZN7android4base11GetProperty*
-__ZN7android4Rect*
-__ZN7android5Fence*
-__ZN7android7RefBase*
-__ZN7android7String*
-__ZN7android10VectorImpl*
-__ZN7android11BufferQueue*
-__ZN7android14AndroidRuntime*
-__ZN7android14sp_report_raceEv*
-__ZN7android15KeyCharacterMap*
-__ZN7android15InputDeviceInfo*
-__ZN7android31android_view_InputDevice_create*
-__ZN7android53android_view_Surface_createFromIGraphicBufferProducer*
diff --git a/core/jni/platform/linux/libandroid_runtime_export.txt b/core/jni/platform/linux/libandroid_runtime_export.txt
deleted file mode 100644
index 19e3478..0000000
--- a/core/jni/platform/linux/libandroid_runtime_export.txt
+++ /dev/null
@@ -1,43 +0,0 @@
-#
-# 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.
-#
-
-{
-  global:
-    # symbols needed for the JNI operations
-    JNI_OnLoad;
-    ANativeWindow*;
-
-    # symbols needed to link with layoutlib_jni
-    __android_log*;
-    _ZNK7android7RefBase*;
-    _ZN7android4base9SetLogger*;
-    _ZN7android4base10SetAborter*;
-    _ZN7android4base11GetProperty*;
-    _ZN7android4Rect*;
-    _ZN7android5Fence*;
-    _ZN7android7RefBase*;
-    _ZN7android7String*;
-    _ZN7android10VectorImpl*;
-    _ZN7android11BufferQueue*;
-    _ZN7android14AndroidRuntime*;
-    _ZN7android14sp_report_raceEv*;
-    _ZN7android15KeyCharacterMap*;
-    _ZN7android15InputDeviceInfo*;
-    _ZN7android31android_view_InputDevice_create*;
-    _ZN7android53android_view_Surface_createFromIGraphicBufferProducer*;
-  local:
-    *;
-};
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index df98952..ed05e6d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8780,6 +8780,20 @@
                 android:featureFlag="com.android.art.flags.executable_method_file_offsets" />
 
     <!--
+        @SystemApi
+        @FlaggedApi(android.content.pm.Flags.FLAG_UID_BASED_PROVIDER_LOOKUP)
+        Allows an app to resolve components (e.g ContentProviders) on behalf of
+        other UIDs
+        <p>Protection level: signature|privileged
+        @hide
+   -->
+    <permission
+        android:name="android.permission.RESOLVE_COMPONENT_FOR_UID"
+        android:protectionLevel="signature|privileged"
+        android:featureFlag="android.content.pm.uid_based_provider_lookup" />
+    <uses-permission android:name="android.permission.RESOLVE_COMPONENT_FOR_UID" />
+
+    <!--
         @TestApi
         Signature permission reserved for testing. This should never be used to
         gate any actual functionality.
diff --git a/core/tests/coretests/res/color/color_with_lstar.xml b/core/tests/coretests/res/color/color_with_lstar.xml
index dcc3d6d..7762fc0 100644
--- a/core/tests/coretests/res/color/color_with_lstar.xml
+++ b/core/tests/coretests/res/color/color_with_lstar.xml
@@ -16,5 +16,5 @@
   -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="#ff0000" android:lStar="50" />
+    <item android:color="@color/testcolor2" android:lStar="50" />
 </selector>
diff --git a/core/tests/coretests/res/values/colors.xml b/core/tests/coretests/res/values/colors.xml
index 029aa0d..f01af84 100644
--- a/core/tests/coretests/res/values/colors.xml
+++ b/core/tests/coretests/res/values/colors.xml
@@ -25,6 +25,5 @@
     <drawable name="yellow">#ffffff00</drawable>
     <color name="testcolor1">#ff00ff00</color>
     <color name="testcolor2">#ffff0000</color>
-    <color name="testcolor3">#fff00000</color>
     <color name="failColor">#ff0000ff</color>
 </resources>
diff --git a/core/tests/coretests/src/android/app/BackgroundStartPrivilegesTest.java b/core/tests/coretests/src/android/app/BackgroundStartPrivilegesTest.java
index cf6266c..931d646 100644
--- a/core/tests/coretests/src/android/app/BackgroundStartPrivilegesTest.java
+++ b/core/tests/coretests/src/android/app/BackgroundStartPrivilegesTest.java
@@ -119,4 +119,15 @@
                 Arrays.asList(BSP_ALLOW_A, BSP_ALLOW_A, BSP_ALLOW_A, BSP_ALLOW_A)))
                 .isEqualTo(BSP_ALLOW_A);
     }
+
+    @Test
+    public void backgroundStartPrivilege_equals_works() {
+        assertThat(NONE).isEqualTo(NONE);
+        assertThat(ALLOW_BAL).isEqualTo(ALLOW_BAL);
+        assertThat(ALLOW_FGS).isEqualTo(ALLOW_FGS);
+        assertThat(BSP_ALLOW_A).isEqualTo(BSP_ALLOW_A);
+        assertThat(NONE).isNotEqualTo(ALLOW_BAL);
+        assertThat(ALLOW_FGS).isNotEqualTo(ALLOW_BAL);
+        assertThat(BSP_ALLOW_A).isNotEqualTo(BSP_ALLOW_B);
+    }
 }
diff --git a/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java b/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java
index 564460e..84bdbe0 100644
--- a/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java
+++ b/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java
@@ -16,19 +16,27 @@
 
 package android.graphics;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
 import android.os.ParcelFileDescriptor;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
 
-public class BitmapFactoryTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class BitmapFactoryTest {
 
     // tests that we can decode bitmaps from MemoryFiles
     @SmallTest
+    @Test
     public void testBitmapParcelFileDescriptor() throws Exception {
         Bitmap bitmap1 = Bitmap.createBitmap(
                 new int[] { Color.BLUE }, 1, 1, Bitmap.Config.RGB_565);
diff --git a/core/tests/coretests/src/android/graphics/BitmapTest.java b/core/tests/coretests/src/android/graphics/BitmapTest.java
index 2280cf1..0126d36 100644
--- a/core/tests/coretests/src/android/graphics/BitmapTest.java
+++ b/core/tests/coretests/src/android/graphics/BitmapTest.java
@@ -16,19 +16,28 @@
 
 package android.graphics;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.hardware.HardwareBuffer;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.nio.ByteBuffer;
 import java.nio.IntBuffer;
 import java.nio.ShortBuffer;
 
-public class BitmapTest extends TestCase {
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BitmapTest {
 
-    @SmallTest
+    @Test
     public void testBasic() throws Exception {
         Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
         Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
@@ -63,7 +72,7 @@
         assertTrue("getConfig", bm3.getConfig() == Bitmap.Config.ARGB_8888);
     }
 
-    @SmallTest
+    @Test
     public void testMutability() throws Exception {
         Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
         Bitmap bm2 = Bitmap.createBitmap(new int[100 * 200], 100, 200,
@@ -82,7 +91,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testGetPixelsWithAlpha() throws Exception {
         int[] colors = new int[100];
         for (int i = 0; i < 100; i++) {
@@ -108,7 +117,7 @@
 
     }
 
-    @SmallTest
+    @Test
     public void testGetPixelsWithoutAlpha() throws Exception {
         int[] colors = new int[100];
         for (int i = 0; i < 100; i++) {
@@ -125,7 +134,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testSetPixelsWithAlpha() throws Exception {
         int[] colors = new int[100];
         for (int i = 0; i < 100; i++) {
@@ -151,7 +160,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testSetPixelsWithoutAlpha() throws Exception {
         int[] colors = new int[100];
         for (int i = 0; i < 100; i++) {
@@ -181,7 +190,7 @@
         return unpre;
     }
 
-    @SmallTest
+    @Test
     public void testSetPixelsWithNonOpaqueAlpha() throws Exception {
         int[] colors = new int[256];
         for (int i = 0; i < 256; i++) {
@@ -238,10 +247,13 @@
         }
     }
 
-    @SmallTest
+    private static final int GRAPHICS_USAGE =
+            GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_SW_READ_OFTEN
+                    | GraphicBuffer.USAGE_SW_WRITE_OFTEN;
+
+    @Test
     public void testWrapHardwareBufferWithSrgbColorSpace() {
-        GraphicBuffer buffer = GraphicBuffer.create(10, 10, PixelFormat.RGBA_8888,
-                GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_SOFTWARE_MASK);
+        GraphicBuffer buffer = GraphicBuffer.create(10, 10, PixelFormat.RGBA_8888, GRAPHICS_USAGE);
         Canvas canvas = buffer.lockCanvas();
         canvas.drawColor(Color.YELLOW);
         buffer.unlockCanvasAndPost(canvas);
@@ -252,10 +264,9 @@
         assertEquals(ColorSpace.get(ColorSpace.Named.SRGB), hardwareBitmap.getColorSpace());
     }
 
-    @SmallTest
+    @Test
     public void testWrapHardwareBufferWithDisplayP3ColorSpace() {
-        GraphicBuffer buffer = GraphicBuffer.create(10, 10, PixelFormat.RGBA_8888,
-                GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_SOFTWARE_MASK);
+        GraphicBuffer buffer = GraphicBuffer.create(10, 10, PixelFormat.RGBA_8888, GRAPHICS_USAGE);
         Canvas canvas = buffer.lockCanvas();
         canvas.drawColor(Color.YELLOW);
         buffer.unlockCanvasAndPost(canvas);
@@ -267,7 +278,7 @@
         assertEquals(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), hardwareBitmap.getColorSpace());
     }
 
-    @SmallTest
+    @Test
     public void testCopyWithDirectByteBuffer() {
         // Initialize Bitmap
         final int width = 2;
@@ -305,7 +316,7 @@
         assertTrue(bm2.sameAs(bm1));
     }
 
-    @SmallTest
+    @Test
     public void testCopyWithDirectShortBuffer() {
         // Initialize Bitmap
         final int width = 2;
@@ -344,7 +355,7 @@
         assertTrue(bm2.sameAs(bm1));
     }
 
-    @SmallTest
+    @Test
     public void testCopyWithDirectIntBuffer() {
         // Initialize Bitmap
         final int width = 2;
@@ -383,7 +394,7 @@
         assertTrue(bm2.sameAs(bm1));
     }
 
-    @SmallTest
+    @Test
     public void testCopyWithHeapByteBuffer() {
         // Initialize Bitmap
         final int width = 2;
@@ -420,7 +431,7 @@
         assertTrue(bm2.sameAs(bm1));
     }
 
-    @SmallTest
+    @Test
     public void testCopyWithHeapShortBuffer() {
         // Initialize Bitmap
         final int width = 2;
@@ -457,7 +468,7 @@
         assertTrue(bm2.sameAs(bm1));
     }
 
-    @SmallTest
+    @Test
     public void testCopyWithHeapIntBuffer() {
         // Initialize Bitmap
         final int width = 2;
diff --git a/core/tests/coretests/src/android/graphics/ColorStateListTest.java b/core/tests/coretests/src/android/graphics/ColorStateListTest.java
index ab41bd0..5cc915e 100644
--- a/core/tests/coretests/src/android/graphics/ColorStateListTest.java
+++ b/core/tests/coretests/src/android/graphics/ColorStateListTest.java
@@ -16,33 +16,41 @@
 
 package android.graphics;
 
+import static org.junit.Assert.assertEquals;
+
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
-import android.test.AndroidTestCase;
 import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.frameworks.coretests.R;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
- * Tests of {@link android.graphics.ColorStateList}
+ * Tests of {@link ColorStateList}
  */
 
-public class ColorStateListTest extends AndroidTestCase {
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ColorStateListTest {
 
     private Resources mResources;
     private int mFailureColor;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mResources = mContext.getResources();
+    @Before
+    public void setUp() throws Exception {
+        mResources = InstrumentationRegistry.getInstrumentation().getContext().getResources();
         mFailureColor = mResources.getColor(R.color.failColor);
     }
 
-    @SmallTest
+    @Test
     public void testStateIsInList() throws Exception {
         ColorStateList colorStateList = mResources.getColorStateList(R.color.color1);
         int[] focusedState = {android.R.attr.state_focused};
@@ -50,7 +58,7 @@
         assertEquals(mResources.getColor(R.color.testcolor1), focusColor);
     }
 
-    @SmallTest
+    @Test
     public void testStateIsInList_proto() throws Exception {
         ColorStateList colorStateList = recreateFromProto(
                 mResources.getColorStateList(R.color.color1));
@@ -59,7 +67,7 @@
         assertEquals(mResources.getColor(R.color.testcolor1), focusColor);
     }
 
-    @SmallTest
+    @Test
     public void testEmptyState() throws Exception {
         ColorStateList colorStateList = mResources.getColorStateList(R.color.color1);
         int[] emptyState = {};
@@ -67,7 +75,7 @@
         assertEquals(mResources.getColor(R.color.testcolor2), defaultColor);
     }
 
-    @SmallTest
+    @Test
     public void testEmptyState_proto() throws Exception {
         ColorStateList colorStateList = recreateFromProto(
                 mResources.getColorStateList(R.color.color1));
@@ -76,22 +84,23 @@
         assertEquals(mResources.getColor(R.color.testcolor2), defaultColor);
     }
 
-    @SmallTest
+    @Test
     public void testGetColor() throws Exception {
         int defaultColor = mResources.getColor(R.color.color1);
         assertEquals(mResources.getColor(R.color.testcolor2), defaultColor);
     }
 
-    @SmallTest
+    @Test
     public void testGetColorWhenListHasNoDefault() throws Exception {
         int defaultColor = mResources.getColor(R.color.color_no_default);
         assertEquals(mResources.getColor(R.color.testcolor1), defaultColor);
     }
 
-    @SmallTest
+    @Test
     public void testLstar() throws Exception {
+        var cl = ColorStateList.valueOf(mResources.getColor(R.color.testcolor2)).withLStar(50.0f);
         int defaultColor = mResources.getColor(R.color.color_with_lstar);
-        assertEquals(mResources.getColor(R.color.testcolor3), defaultColor);
+        assertEquals(cl.getDefaultColor(), defaultColor);
     }
 
     private ColorStateList recreateFromProto(ColorStateList colorStateList) throws Exception {
diff --git a/core/tests/coretests/src/android/graphics/FontFileUtilTest.java b/core/tests/coretests/src/android/graphics/FontFileUtilTest.java
index 52cc4ca..063bdf5 100644
--- a/core/tests/coretests/src/android/graphics/FontFileUtilTest.java
+++ b/core/tests/coretests/src/android/graphics/FontFileUtilTest.java
@@ -30,9 +30,11 @@
 import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -43,6 +45,7 @@
 import java.nio.channels.FileChannel;
 
 @SmallTest
+@RunWith(AndroidJUnit4.class)
 public class FontFileUtilTest {
     private static final String TAG = "FontFileUtilTest";
     private static final String CACHE_FILE_PREFIX = ".font";
diff --git a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
index 8a54e5b..816bde6 100644
--- a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
@@ -21,10 +21,9 @@
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.test.InstrumentationTestCase;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.text.flags.Flags;
 
@@ -37,7 +36,7 @@
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class PaintFontVariationTest extends InstrumentationTestCase {
+public class PaintFontVariationTest {
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index 878ba70..56760d7 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -18,19 +18,26 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.test.InstrumentationTestCase;
 import android.text.TextUtils;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.text.flags.Flags;
 
 import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.Arrays;
 import java.util.HashSet;
@@ -38,13 +45,14 @@
 /**
  * PaintTest tests {@link Paint}.
  */
-public class PaintTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class PaintTest {
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     private static final String FONT_PATH = "fonts/HintedAdvanceWidthTest-Regular.ttf";
 
-    static void assertEquals(String message, float[] expected, float[] actual) {
+    static void assertFloatArrayEquals(String message, float[] expected, float[] actual) {
         if (expected.length != actual.length) {
             fail(message + " expected array length:<" + expected.length + "> but was:<"
                     + actual.length + ">");
@@ -88,9 +96,10 @@
     };
 
     @SmallTest
+    @Test
     public void testHintingWidth() {
         final Typeface fontTypeface = Typeface.createFromAsset(
-                getInstrumentation().getContext().getAssets(), FONT_PATH);
+                InstrumentationRegistry.getInstrumentation().getContext().getAssets(), FONT_PATH);
         Paint paint = new Paint();
         paint.setTypeface(fontTypeface);
 
@@ -103,12 +112,14 @@
 
             paint.setHinting(Paint.HINTING_OFF);
             paint.getTextWidths(String.valueOf(testCase.mText), widths);
-            assertEquals("Text width of '" + testCase.mText + "' without hinting is not expected.",
+            assertFloatArrayEquals(
+                    "Text width of '" + testCase.mText + "' without hinting is not expected.",
                     testCase.mWidthWithoutHinting, widths);
 
             paint.setHinting(Paint.HINTING_ON);
             paint.getTextWidths(String.valueOf(testCase.mText), widths);
-            assertEquals("Text width of '" + testCase.mText + "' with hinting is not expected.",
+            assertFloatArrayEquals(
+                    "Text width of '" + testCase.mText + "' with hinting is not expected.",
                     testCase.mWidthWithHinting, widths);
         }
     }
@@ -131,9 +142,11 @@
         return sb.toString();
     }
 
+    @Test
     public void testHasGlyph_variationSelectors() {
         final Typeface fontTypeface = Typeface.createFromAsset(
-                getInstrumentation().getContext().getAssets(), "fonts/hasGlyphTestFont.ttf");
+                InstrumentationRegistry.getInstrumentation().getContext().getAssets(),
+                "fonts/hasGlyphTestFont.ttf");
         Paint p = new Paint();
         p.setTypeface(fontTypeface);
 
@@ -175,6 +188,7 @@
         }
     }
 
+    @Test
     public void testGetTextRunAdvances() {
         {
             // LTR
@@ -231,6 +245,7 @@
         }
     }
 
+    @Test
     public void testGetTextRunAdvances_invalid() {
         Paint p = new Paint();
         char[] text = "test".toCharArray();
@@ -284,6 +299,7 @@
         }
     }
 
+    @Test
     public void testMeasureTextBidi() {
         Paint p = new Paint();
         {
@@ -340,18 +356,21 @@
         }
     }
 
+    @Test
     public void testSetGetWordSpacing() {
         Paint p = new Paint();
-        assertEquals(0.0f, p.getWordSpacing());  // The default value should be 0.
+        assertEquals(0.0f, p.getWordSpacing(), 0.0f);  // The default value should be 0.
         p.setWordSpacing(1.0f);
-        assertEquals(1.0f, p.getWordSpacing());
+        assertEquals(1.0f, p.getWordSpacing(), 0.0f);
         p.setWordSpacing(-2.0f);
-        assertEquals(-2.0f, p.getWordSpacing());
+        assertEquals(-2.0f, p.getWordSpacing(), 0.0f);
     }
 
+    @Test
     public void testGetUnderlinePositionAndThickness() {
         final Typeface fontTypeface = Typeface.createFromAsset(
-                getInstrumentation().getContext().getAssets(), "fonts/underlineTestFont.ttf");
+                InstrumentationRegistry.getInstrumentation().getContext().getAssets(),
+                "fonts/underlineTestFont.ttf");
         final Paint p = new Paint();
         final int textSize = 100;
         p.setTextSize(textSize);
@@ -391,6 +410,7 @@
         return ccByChars;
     }
 
+    @Test
     public void testCluster() {
         final Paint p = new Paint();
         p.setTextSize(100);
@@ -417,6 +437,7 @@
     }
 
     @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+    @Test
     public void testDerivedFromSameTypeface() {
         final Paint p = new Paint();
 
@@ -432,6 +453,7 @@
     }
 
     @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+    @Test
     public void testDerivedFromChained() {
         final Paint p = new Paint();
 
diff --git a/core/tests/coretests/src/android/graphics/ThreadBitmapTest.java b/core/tests/coretests/src/android/graphics/ThreadBitmapTest.java
index e1ca7df..fbaf502 100644
--- a/core/tests/coretests/src/android/graphics/ThreadBitmapTest.java
+++ b/core/tests/coretests/src/android/graphics/ThreadBitmapTest.java
@@ -16,17 +16,17 @@
 
 package android.graphics;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 
-import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class ThreadBitmapTest extends TestCase {
-
-    @Override
-    protected void setUp() throws Exception {
-    }
+@RunWith(AndroidJUnit4.class)
+public class ThreadBitmapTest {
 
     @LargeTest
+    @Test
     public void testCreation() {
         for (int i = 0; i < 200; i++) {
 
@@ -44,4 +44,3 @@
         public void run() {}
     }
 }
-
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 1429272..2b6eda8f 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -39,6 +39,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.text.flags.Flags;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -514,9 +516,14 @@
         assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
 
         paint.setElegantTextHeight(false);
-        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f);
-        assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0.0f);
-        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
+        if (Flags.deprecateElegantTextHeightApi()) {
+            // Calling setElegantTextHeight is no-op.
+            assertTrue(paint.isElegantTextHeight());
+        } else {
+            assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f);
+            assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0.0f);
+            assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
+        }
     }
 
     @Test
@@ -553,9 +560,14 @@
         assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
 
         paint.setElegantTextHeight(false);
-        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f);
-        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
-        assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 0.0f);
+        if (Flags.deprecateElegantTextHeightApi()) {
+            // Calling setElegantTextHeight is no-op.
+            assertTrue(paint.isElegantTextHeight());
+        } else {
+            assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f);
+            assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
+            assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 0.0f);
+        }
 
         testTypeface = fontMap.get("sans-serif");
         assertNotNull(testTypeface);
@@ -566,9 +578,14 @@
         assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
 
         paint.setElegantTextHeight(false);
-        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f);
-        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
-        assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 0.0f);
+        if (Flags.deprecateElegantTextHeightApi()) {
+            // Calling setElegantTextHeight is no-op.
+            assertTrue(paint.isElegantTextHeight());
+        } else {
+            assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f);
+            assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
+            assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 0.0f);
+        }
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/security/advancedprotection/AdvancedProtectionManagerTest.java b/core/tests/coretests/src/android/security/advancedprotection/AdvancedProtectionManagerTest.java
new file mode 100644
index 0000000..45864b0
--- /dev/null
+++ b/core/tests/coretests/src/android/security/advancedprotection/AdvancedProtectionManagerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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 android.security.advancedprotection;
+
+import static android.security.advancedprotection.AdvancedProtectionManager.ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG;
+import static android.security.advancedprotection.AdvancedProtectionManager.EXTRA_SUPPORT_DIALOG_FEATURE;
+import static android.security.advancedprotection.AdvancedProtectionManager.EXTRA_SUPPORT_DIALOG_TYPE;
+import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G;
+import static android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION;
+import static android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_DISABLED_SETTING;
+import static android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_UNKNOWN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.content.Intent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AdvancedProtectionManagerTest {
+    private static final int FEATURE_ID_INVALID = -1;
+    private static final int SUPPORT_DIALOG_TYPE_INVALID = -1;
+
+    @Test
+    public void testCreateSupportIntent_validFeature_validTypeUnknown_createsIntent() {
+        Intent intent = AdvancedProtectionManager.createSupportIntent(
+                FEATURE_ID_DISALLOW_CELLULAR_2G, SUPPORT_DIALOG_TYPE_UNKNOWN);
+
+        assertEquals(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG, intent.getAction());
+        assertEquals(FEATURE_ID_DISALLOW_CELLULAR_2G, intent.getIntExtra(
+                EXTRA_SUPPORT_DIALOG_FEATURE, FEATURE_ID_INVALID));
+        assertEquals(SUPPORT_DIALOG_TYPE_UNKNOWN, intent.getIntExtra(EXTRA_SUPPORT_DIALOG_TYPE,
+                SUPPORT_DIALOG_TYPE_INVALID));
+    }
+
+    @Test
+    public void testCreateSupportIntent_validFeature_validTypeBlockedInteraction_createsIntent() {
+        Intent intent = AdvancedProtectionManager.createSupportIntent(
+                FEATURE_ID_DISALLOW_CELLULAR_2G, SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION);
+
+        assertEquals(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG, intent.getAction());
+        assertEquals(FEATURE_ID_DISALLOW_CELLULAR_2G, intent.getIntExtra(
+                EXTRA_SUPPORT_DIALOG_FEATURE, FEATURE_ID_INVALID));
+        assertEquals(SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION, intent.getIntExtra(
+                EXTRA_SUPPORT_DIALOG_TYPE, SUPPORT_DIALOG_TYPE_INVALID));
+    }
+
+    @Test
+    public void testCreateSupportIntent_validFeature_validTypeDisabledSetting_createsIntent() {
+        Intent intent = AdvancedProtectionManager.createSupportIntent(
+                FEATURE_ID_DISALLOW_CELLULAR_2G, SUPPORT_DIALOG_TYPE_DISABLED_SETTING);
+
+        assertEquals(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG, intent.getAction());
+        assertEquals(FEATURE_ID_DISALLOW_CELLULAR_2G, intent.getIntExtra(
+                EXTRA_SUPPORT_DIALOG_FEATURE, FEATURE_ID_INVALID));
+        assertEquals(SUPPORT_DIALOG_TYPE_DISABLED_SETTING, intent.getIntExtra(
+                EXTRA_SUPPORT_DIALOG_TYPE, SUPPORT_DIALOG_TYPE_INVALID));
+    }
+
+    @Test
+    public void testCreateSupportIntent_validFeature_invalidType_throwsIllegalArgument() {
+        assertThrows(IllegalArgumentException.class, () ->
+                AdvancedProtectionManager.createSupportIntent(FEATURE_ID_DISALLOW_CELLULAR_2G,
+                        SUPPORT_DIALOG_TYPE_INVALID));
+    }
+
+    @Test
+    public void testCreateSupportIntent_invalidFeature_validType_throwsIllegalArgument() {
+        assertThrows(IllegalArgumentException.class, () ->
+                AdvancedProtectionManager.createSupportIntent(FEATURE_ID_INVALID,
+                        SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION));
+    }
+
+    @Test
+    public void testCreateSupportIntent_invalidFeature_invalidType_throwsIllegalArgument() {
+        assertThrows(IllegalArgumentException.class, () ->
+                AdvancedProtectionManager.createSupportIntent(FEATURE_ID_INVALID,
+                        SUPPORT_DIALOG_TYPE_INVALID));
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index fb1efa8..8b4f714 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -1171,6 +1171,88 @@
         waitForAfterDraw();
     }
 
+    @Test
+    public void ignoreHeuristicWhenFling() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
+
+        waitForFrameRateCategoryToSettle();
+        FrameLayout host = new FrameLayout(mActivity);
+        View childView = new View(mActivity);
+        float velocity = 1000;
+
+        TranslateAnimation translateAnimation = new TranslateAnimation(
+                Animation.RELATIVE_TO_PARENT, 0f, // fromXDelta
+                Animation.RELATIVE_TO_PARENT, 0f, // toXDelta
+                Animation.RELATIVE_TO_PARENT, 1f, // fromYDelta (100%p)
+                Animation.RELATIVE_TO_PARENT, 0f  // toYDelta
+        );
+        translateAnimation.setDuration(100);
+
+        mActivityRule.runOnUiThread(() -> {
+            ViewGroup.LayoutParams fullSize = new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT);
+            mActivity.setContentView(host, fullSize);
+            host.setFrameContentVelocity(velocity);
+            ViewGroupOverlay overlay = host.getOverlay();
+            overlay.add(childView);
+            assertEquals(velocity, host.getFrameContentVelocity());
+            assertEquals(host.getFrameContentVelocity(),
+                    ((View) childView.getParent()).getFrameContentVelocity());
+
+            mMovingView.startAnimation(translateAnimation);
+
+            // The frame rate should be "Normal" during fling gestures,
+            // even if there's a moving View.
+            assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+                    mViewRoot.getLastPreferredFrameRateCategory());
+        });
+        waitForAfterDraw();
+    }
+
+    @Test
+    public void ignoreHeuristicWhenFlingMovementFirst() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
+
+        waitForFrameRateCategoryToSettle();
+        FrameLayout host = new FrameLayout(mActivity);
+        View childView = new View(mActivity);
+        float velocity = 1000;
+
+        TranslateAnimation translateAnimation = new TranslateAnimation(
+                Animation.RELATIVE_TO_PARENT, 0f, // fromXDelta
+                Animation.RELATIVE_TO_PARENT, 0f, // toXDelta
+                Animation.RELATIVE_TO_PARENT, 1f, // fromYDelta (100%p)
+                Animation.RELATIVE_TO_PARENT, 0f  // toYDelta
+        );
+        translateAnimation.setDuration(100);
+
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.startAnimation(translateAnimation);
+
+            ViewGroup.LayoutParams fullSize = new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT);
+            mActivity.setContentView(host, fullSize);
+            host.setFrameContentVelocity(velocity);
+            ViewGroupOverlay overlay = host.getOverlay();
+            overlay.add(childView);
+            assertEquals(velocity, host.getFrameContentVelocity());
+            assertEquals(host.getFrameContentVelocity(),
+                    ((View) childView.getParent()).getFrameContentVelocity());
+
+            // The frame rate should be "Normal" during fling gestures,
+            // even if there's a moving View.
+            assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+                    mViewRoot.getLastPreferredFrameRateCategory());
+        });
+        waitForAfterDraw();
+    }
+
     private void runAfterDraw(@NonNull Runnable runnable) {
         Handler handler = new Handler(Looper.getMainLooper());
         mAfterDrawLatch = new CountDownLatch(1);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2398e71..f136e06 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -125,6 +125,7 @@
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
         <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+        <permission name="android.permission.RESOLVE_COMPONENT_FOR_UID"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.phone">
@@ -609,6 +610,8 @@
         <permission name="android.permission.MANAGE_INTRUSION_DETECTION_STATE" />
         <!-- Permission required for CTS test - KeyguardLockedStateApiTest -->
         <permission name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
+        <!-- Permission required for CTS test - CtsContentProviderMultiUserTest -->
+        <permission name="android.permission.RESOLVE_COMPONENT_FOR_UID"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 5f1fb4b..50054850 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -171,3 +171,10 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "task_view_repository"
+    namespace: "multitasking"
+    description: "Factor task-view state tracking out of taskviewtransitions"
+    bug: "384976265"
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
index b141beb..1cc58c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
@@ -32,6 +32,7 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.window.flags.Flags;
 import com.android.wm.shell.R;
 
 /**
@@ -172,6 +173,9 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 view.setVisibility(View.GONE);
+                if (Flags.releaseUserAspectRatioWm()) {
+                    mWindowManager.release();
+                }
             }
         });
         fadeOut.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index ff6fb59..ceef699 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -541,6 +541,9 @@
             TASK_FINISHED(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__TASK_FINISHED),
             SCREEN_OFF(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF),
             TASK_MINIMIZED(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__TASK_MINIMIZED),
+            TASK_MOVED_TO_BACK(
+                FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__TASK_MOVED_TO_BACK
+            ),
         }
 
         /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 9a60cfe..ccfbbee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -404,12 +404,17 @@
                 wasPreviousTransitionExitByScreenOff = true
                 ExitReason.SCREEN_OFF
             }
+            // TODO(b/384490301): differentiate back gesture / button exit from clicking the close
+            // button located in the window top corner.
+            transitionInfo.type == WindowManager.TRANSIT_TO_BACK -> ExitReason.TASK_MOVED_TO_BACK
             transitionInfo.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED
             transitionInfo.type == TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG -> ExitReason.DRAG_TO_EXIT
             transitionInfo.type == TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON ->
                 ExitReason.APP_HANDLE_MENU_BUTTON_EXIT
+
             transitionInfo.type == TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT ->
                 ExitReason.KEYBOARD_SHORTCUT_EXIT
+
             transitionInfo.isExitToRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW
             transitionInfo.type == Transitions.TRANSIT_MINIMIZE -> ExitReason.TASK_MINIMIZED
             else -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 45faba6..0330a5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -127,14 +127,20 @@
 
         override fun onTransitionStarting(transition: IBinder) {
             val mActiveTaskDetails = activeTransitionTokensAndTasks[transition]
-            if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) {
-                // Begin minimize window CUJ instrumentation.
-                interactionJankMonitor.begin(
-                    mActiveTaskDetails.transitionInfo?.rootLeash,
-                    context,
-                    handler,
-                    CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
-                )
+            val info = mActiveTaskDetails?.transitionInfo ?: return
+            val minimizeChange = getMinimizeChange(info, mActiveTaskDetails.taskId) ?: return
+            // Begin minimize window CUJ instrumentation.
+            interactionJankMonitor.begin(
+                minimizeChange.leash,
+                context,
+                handler,
+                CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
+            )
+        }
+
+        private fun getMinimizeChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? {
+            return info.changes.find { change ->
+                change.taskInfo?.taskId == taskId && change.mode == TRANSIT_TO_BACK
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
new file mode 100644
index 0000000..0d75e65
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.shared.FocusTransitionListener;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.FocusTransitionObserver;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
+
+/**
+ * Works with decorations that extend {@link CarWindowDecoration}.
+ */
+public abstract class CarWindowDecorViewModel
+        implements WindowDecorViewModel, FocusTransitionListener {
+    private static final String TAG = "CarWindowDecorViewModel";
+
+    private final ShellTaskOrganizer mTaskOrganizer;
+    private final Context mContext;
+    private final @ShellBackgroundThread ShellExecutor mBgExecutor;
+    private final ShellExecutor mMainExecutor;
+    private final DisplayController mDisplayController;
+    private final FocusTransitionObserver mFocusTransitionObserver;
+    private final SyncTransactionQueue mSyncQueue;
+    private final SparseArray<CarWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
+    private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier;
+
+    public CarWindowDecorViewModel(
+            Context context,
+            @ShellBackgroundThread ShellExecutor bgExecutor,
+            @ShellMainThread ShellExecutor shellExecutor,
+            ShellInit shellInit,
+            ShellTaskOrganizer taskOrganizer,
+            DisplayController displayController,
+            SyncTransactionQueue syncQueue,
+            FocusTransitionObserver focusTransitionObserver,
+            WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
+        mContext = context;
+        mMainExecutor = shellExecutor;
+        mBgExecutor = bgExecutor;
+        mTaskOrganizer = taskOrganizer;
+        mDisplayController = displayController;
+        mFocusTransitionObserver = focusTransitionObserver;
+        mSyncQueue = syncQueue;
+        mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
+
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    private void onInit() {
+        mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
+    }
+
+    @Override
+    public void onFocusedTaskChanged(int taskId, boolean isFocusedOnDisplay,
+            boolean isFocusedGlobally) {
+        // no-op
+    }
+
+    @Override
+    public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
+        // no-op
+    }
+
+    @Override
+    public void setSplitScreenController(SplitScreenController splitScreenController) {
+        // no-op
+    }
+
+    @Override
+    public boolean onTaskOpening(
+            RunningTaskInfo taskInfo,
+            SurfaceControl taskSurface,
+            SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT) {
+        if (!shouldShowWindowDecor(taskInfo)) {
+            return false;
+        }
+        createWindowDecoration(taskInfo, taskSurface, startT, finishT);
+        return true;
+    }
+
+    @Override
+    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+        final CarWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
+        if (decoration == null) {
+            return;
+        }
+
+        if (!shouldShowWindowDecor(taskInfo)) {
+            destroyWindowDecoration(taskInfo);
+            return;
+        }
+
+        decoration.relayout(taskInfo, decoration.mHasGlobalFocus, decoration.mExclusionRegion);
+    }
+
+    @Override
+    public void onTaskVanished(RunningTaskInfo taskInfo) {
+        // A task vanishing doesn't necessarily mean the task was closed, it could also mean its
+        // windowing mode changed. We're only interested in closing tasks so checking whether
+        // its info still exists in the task organizer is one way to disambiguate.
+        final boolean closed = mTaskOrganizer.getRunningTaskInfo(taskInfo.taskId) == null;
+        if (closed) {
+            // Destroying the window decoration is usually handled when a TRANSIT_CLOSE transition
+            // changes happen, but there are certain cases in which closing tasks aren't included
+            // in transitions, such as when a non-visible task is closed. See b/296921167.
+            // Destroy the decoration here in case the lack of transition missed it.
+            destroyWindowDecoration(taskInfo);
+        }
+    }
+
+    @Override
+    public void onTaskChanging(
+            RunningTaskInfo taskInfo,
+            SurfaceControl taskSurface,
+            SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT) {
+        final CarWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
+        if (!shouldShowWindowDecor(taskInfo)) {
+            if (decoration != null) {
+                destroyWindowDecoration(taskInfo);
+            }
+            return;
+        }
+
+        if (decoration == null) {
+            createWindowDecoration(taskInfo, taskSurface, startT, finishT);
+        } else {
+            decoration.relayout(taskInfo, startT, finishT);
+        }
+    }
+
+    @Override
+    public void onTaskClosing(
+            RunningTaskInfo taskInfo,
+            SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT) {
+        final CarWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+        if (decoration == null) {
+            return;
+        }
+        decoration.relayout(taskInfo, startT, finishT);
+    }
+
+    @Override
+    public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
+        final CarWindowDecoration decoration =
+                mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
+        if (decoration == null) {
+            return;
+        }
+
+        decoration.close();
+    }
+
+    /**
+     * @return {@code true} if the task/activity associated with {@code taskInfo} should show
+     * window decoration.
+     */
+    protected abstract boolean shouldShowWindowDecor(RunningTaskInfo taskInfo);
+
+    private void createWindowDecoration(
+            RunningTaskInfo taskInfo,
+            SurfaceControl taskSurface,
+            SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT) {
+        final CarWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+        if (oldDecoration != null) {
+            // close the old decoration if it exists to avoid two window decorations being added
+            oldDecoration.close();
+        }
+        final CarWindowDecoration windowDecoration =
+                new CarWindowDecoration(
+                        mContext,
+                        mContext.createContextAsUser(UserHandle.of(taskInfo.userId), 0 /* flags */),
+                        mDisplayController,
+                        mTaskOrganizer,
+                        taskInfo,
+                        taskSurface,
+                        mBgExecutor,
+                        mWindowDecorViewHostSupplier,
+                        new ButtonClickListener(taskInfo));
+        mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+        windowDecoration.relayout(taskInfo, startT, finishT);
+    }
+
+    private class ButtonClickListener implements View.OnClickListener {
+        private final WindowContainerToken mTaskToken;
+        private final int mDisplayId;
+
+        private ButtonClickListener(RunningTaskInfo taskInfo) {
+            mTaskToken = taskInfo.token;
+            mDisplayId = taskInfo.displayId;
+        }
+
+        @Override
+        public void onClick(View v) {
+            final int id = v.getId();
+            if (id == R.id.close_window) {
+                WindowContainerTransaction wct = new WindowContainerTransaction();
+                wct.removeTask(mTaskToken);
+                mSyncQueue.queue(wct);
+            } else if (id == R.id.back_button) {
+                sendBackEvent(KeyEvent.ACTION_DOWN, mDisplayId);
+                sendBackEvent(KeyEvent.ACTION_UP, mDisplayId);
+            }
+        }
+
+        private void sendBackEvent(int action, int displayId) {
+            final long when = SystemClock.uptimeMillis();
+            final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK,
+                    0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD,
+                    0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+                    InputDevice.SOURCE_KEYBOARD);
+
+            ev.setDisplayId(displayId);
+            if (!mContext.getSystemService(InputManager.class)
+                    .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
+                Log.e(TAG, "Inject input event fail");
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
new file mode 100644
index 0000000..1ca82d2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor;
+
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
+
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
+
+/**
+ * {@link WindowDecoration} to show app controls for windows on automotive.
+ */
+public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
+    private WindowDecorLinearLayout mRootView;
+    private @ShellBackgroundThread final ShellExecutor mBgExecutor;
+    private final View.OnClickListener mClickListener;
+
+    CarWindowDecoration(
+            Context context,
+            @android.annotation.NonNull Context userContext,
+            DisplayController displayController,
+            ShellTaskOrganizer taskOrganizer,
+            ActivityManager.RunningTaskInfo taskInfo,
+            SurfaceControl taskSurface,
+            @ShellBackgroundThread ShellExecutor bgExecutor,
+            WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
+            View.OnClickListener clickListener) {
+        super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface,
+                windowDecorViewHostSupplier);
+        mBgExecutor = bgExecutor;
+        mClickListener = clickListener;
+    }
+
+    @Override
+    void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus,
+            @NonNull Region displayExclusionRegion) {
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        relayout(taskInfo, t, t);
+    }
+
+    @SuppressLint("MissingPermission")
+    void relayout(ActivityManager.RunningTaskInfo taskInfo,
+            SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+        RelayoutParams relayoutParams = new RelayoutParams();
+        RelayoutResult<WindowDecorLinearLayout> outResult = new RelayoutResult<>();
+
+        updateRelayoutParams(relayoutParams, taskInfo,
+                mDisplayController.getInsetsState(taskInfo.displayId));
+
+        relayout(relayoutParams, startT, finishT, wct, mRootView, outResult);
+        // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
+        mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct));
+
+        if (outResult.mRootView == null) {
+            // This means something blocks the window decor from showing, e.g. the task is hidden.
+            // Nothing is set up in this case including the decoration surface.
+            return;
+        }
+        if (mRootView != outResult.mRootView) {
+            mRootView = outResult.mRootView;
+            setupRootView(outResult.mRootView, mClickListener);
+        }
+    }
+
+    @Override
+    @NonNull
+    Rect calculateValidDragArea() {
+        return new Rect();
+    }
+
+    @Override
+    int getCaptionViewId() {
+        return R.id.caption;
+    }
+
+    private void updateRelayoutParams(
+            RelayoutParams relayoutParams,
+            ActivityManager.RunningTaskInfo taskInfo,
+            InsetsState displayInsetsState) {
+        relayoutParams.reset();
+        relayoutParams.mRunningTaskInfo = taskInfo;
+        // todo(b/382071404): update to car specific UI
+        relayoutParams.mLayoutResId = R.layout.caption_window_decor;
+        relayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
+        relayoutParams.mIsCaptionVisible = mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded;
+        relayoutParams.mCaptionTopPadding = 0;
+        relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
+        relayoutParams.mApplyStartTransactionOnDraw = true;
+    }
+
+    /**
+     * Sets up listeners when a new root view is created.
+     */
+    private void setupRootView(View rootView, View.OnClickListener onClickListener) {
+        final View caption = rootView.findViewById(R.id.caption);
+        final View close = caption.findViewById(R.id.close_window);
+        if (close != null) {
+            close.setOnClickListener(onClickListener);
+        }
+        final View back = caption.findViewById(R.id.back_button);
+        if (back != null) {
+            back.setOnClickListener(onClickListener);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 7928e5e..a7a5f09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -935,10 +935,12 @@
                 //  back to the decoration using
                 //  {@link DesktopModeWindowDecoration#setOnMaximizeOrRestoreClickListener}, which
                 //  should shared with the maximize menu's maximize/restore actions.
+                final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile(
+                        decoration.mTaskInfo.userId);
                 if (Flags.enableFullyImmersiveInDesktop()
-                        && TaskInfoKt.getRequestingImmersive(decoration.mTaskInfo)) {
-                    // Task is requesting immersive, so it should either enter or exit immersive,
-                    // depending on immersive state.
+                        && desktopRepository.isTaskInFullImmersiveState(
+                                decoration.mTaskInfo.taskId)) {
+                    // Task is in immersive and should exit.
                     onEnterOrExitImmersive(decoration.mTaskInfo);
                 } else {
                     // Full immersive is disabled or task doesn't request/support it, so just
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 0154ff3..9d41013 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -509,6 +509,20 @@
     }
 
     @Test
+    fun transitExitBackGesture_logTaskRemovedAndExitReasonTaskMovedToBack() {
+        // add a freeform task
+        transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+        transitionObserver.isSessionActive = true
+
+        // task moved to back
+        val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM))
+        val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_BACK).addChange(change).build()
+        callOnTransitionReady(transitionInfo)
+
+        verifyTaskRemovedAndExitLogging(ExitReason.TASK_MOVED_TO_BACK, DEFAULT_TASK_UPDATE)
+    }
+
+    @Test
     fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown() {
         // add a freeform task
         transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index e6f1fcf..52602f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -551,7 +551,7 @@
             .getTransitionObserver()
             .onTransitionReady(
                 transition,
-                TransitionInfoBuilder(TRANSIT_OPEN).build(),
+                TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
                 StubTransaction() /* startTransaction */,
                 StubTransaction(), /* finishTransaction */
             )
@@ -583,7 +583,7 @@
             .getTransitionObserver()
             .onTransitionReady(
                 transition,
-                TransitionInfoBuilder(TRANSIT_OPEN).build(),
+                TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
                 StubTransaction() /* startTransaction */,
                 StubTransaction(), /* finishTransaction */
             )
@@ -616,7 +616,7 @@
             .getTransitionObserver()
             .onTransitionReady(
                 mergedTransition,
-                TransitionInfoBuilder(TRANSIT_OPEN).build(),
+                TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
                 StubTransaction() /* startTransaction */,
                 StubTransaction(), /* finishTransaction */
             )
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index aead0a7..ffe8e71 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -1054,26 +1054,6 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
-    fun testImmersiveButtonClick_entersImmersiveMode() {
-        val onClickListenerCaptor = forClass(View.OnClickListener::class.java)
-                as ArgumentCaptor<View.OnClickListener>
-        val decor = createOpenTaskDecoration(
-            windowingMode = WINDOWING_MODE_FREEFORM,
-            onCaptionButtonClickListener = onClickListenerCaptor,
-            requestingImmersive = true,
-        )
-        val view = mock(View::class.java)
-        whenever(view.id).thenReturn(R.id.maximize_window)
-        whenever(mockDesktopRepository.isTaskInFullImmersiveState(decor.mTaskInfo.taskId))
-            .thenReturn(false)
-
-        onClickListenerCaptor.value.onClick(view)
-
-        verify(mockDesktopImmersiveController).moveTaskToImmersive(decor.mTaskInfo)
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
     fun testImmersiveRestoreButtonClick_exitsImmersiveMode() {
         val onClickListenerCaptor = forClass(View.OnClickListener::class.java)
                 as ArgumentCaptor<View.OnClickListener>
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index 023bad2..eb19ba8 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -1,6 +1,29 @@
 // Signature format: 2.0
 package android.location {
 
+  @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class AuxiliaryInformation implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.location.GnssSignalType> getAvailableSignalTypes();
+    method @IntRange(from=0xfffffff9, to=6) public int getFrequencyChannelNumber();
+    method public int getSatType();
+    method @IntRange(from=1) public int getSvid();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int BDS_B1C_ORBIT_TYPE_GEO = 1; // 0x1
+    field public static final int BDS_B1C_ORBIT_TYPE_IGSO = 2; // 0x2
+    field public static final int BDS_B1C_ORBIT_TYPE_MEO = 3; // 0x3
+    field public static final int BDS_B1C_ORBIT_TYPE_UNDEFINED = 0; // 0x0
+    field @NonNull public static final android.os.Parcelable.Creator<android.location.AuxiliaryInformation> CREATOR;
+  }
+
+  public static final class AuxiliaryInformation.Builder {
+    ctor public AuxiliaryInformation.Builder();
+    method @NonNull public android.location.AuxiliaryInformation build();
+    method @NonNull public android.location.AuxiliaryInformation.Builder setAvailableSignalTypes(@NonNull java.util.List<android.location.GnssSignalType>);
+    method @NonNull public android.location.AuxiliaryInformation.Builder setFrequencyChannelNumber(@IntRange(from=0xfffffff9, to=6) int);
+    method @NonNull public android.location.AuxiliaryInformation.Builder setSatType(int);
+    method @NonNull public android.location.AuxiliaryInformation.Builder setSvid(@IntRange(from=1) int);
+  }
+
   public abstract class BatchedLocationCallback {
     ctor public BatchedLocationCallback();
     method public void onLocationBatch(java.util.List<android.location.Location>);
@@ -9,6 +32,7 @@
   @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class BeidouAssistance implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.location.GnssAlmanac getAlmanac();
+    method @Nullable public android.location.AuxiliaryInformation getAuxiliaryInformation();
     method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel();
     method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel();
     method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels();
@@ -24,12 +48,13 @@
     ctor public BeidouAssistance.Builder();
     method @NonNull public android.location.BeidouAssistance build();
     method @NonNull public android.location.BeidouAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac);
+    method @NonNull public android.location.BeidouAssistance.Builder setAuxiliaryInformation(@Nullable android.location.AuxiliaryInformation);
     method @NonNull public android.location.BeidouAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel);
     method @NonNull public android.location.BeidouAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel);
-    method @NonNull public android.location.BeidouAssistance.Builder setRealTimeIntegrityModels(@Nullable java.util.List<android.location.RealTimeIntegrityModel>);
-    method @NonNull public android.location.BeidouAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
-    method @NonNull public android.location.BeidouAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.BeidouSatelliteEphemeris>);
-    method @NonNull public android.location.BeidouAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>);
+    method @NonNull public android.location.BeidouAssistance.Builder setRealTimeIntegrityModels(@NonNull java.util.List<android.location.RealTimeIntegrityModel>);
+    method @NonNull public android.location.BeidouAssistance.Builder setSatelliteCorrections(@NonNull java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
+    method @NonNull public android.location.BeidouAssistance.Builder setSatelliteEphemeris(@NonNull java.util.List<android.location.BeidouSatelliteEphemeris>);
+    method @NonNull public android.location.BeidouAssistance.Builder setTimeModels(@NonNull java.util.List<android.location.TimeModel>);
     method @NonNull public android.location.BeidouAssistance.Builder setUtcModel(@Nullable android.location.UtcModel);
   }
 
@@ -151,6 +176,7 @@
   @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GalileoAssistance implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.location.GnssAlmanac getAlmanac();
+    method @Nullable public android.location.AuxiliaryInformation getAuxiliaryInformation();
     method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel();
     method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel();
     method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels();
@@ -166,12 +192,13 @@
     ctor public GalileoAssistance.Builder();
     method @NonNull public android.location.GalileoAssistance build();
     method @NonNull public android.location.GalileoAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac);
+    method @NonNull public android.location.GalileoAssistance.Builder setAuxiliaryInformation(@Nullable android.location.AuxiliaryInformation);
     method @NonNull public android.location.GalileoAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel);
     method @NonNull public android.location.GalileoAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel);
-    method @NonNull public android.location.GalileoAssistance.Builder setRealTimeIntegrityModels(@Nullable java.util.List<android.location.RealTimeIntegrityModel>);
-    method @NonNull public android.location.GalileoAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
-    method @NonNull public android.location.GalileoAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.GalileoSatelliteEphemeris>);
-    method @NonNull public android.location.GalileoAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>);
+    method @NonNull public android.location.GalileoAssistance.Builder setRealTimeIntegrityModels(@NonNull java.util.List<android.location.RealTimeIntegrityModel>);
+    method @NonNull public android.location.GalileoAssistance.Builder setSatelliteCorrections(@NonNull java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
+    method @NonNull public android.location.GalileoAssistance.Builder setSatelliteEphemeris(@NonNull java.util.List<android.location.GalileoSatelliteEphemeris>);
+    method @NonNull public android.location.GalileoAssistance.Builder setTimeModels(@NonNull java.util.List<android.location.TimeModel>);
     method @NonNull public android.location.GalileoAssistance.Builder setUtcModel(@Nullable android.location.UtcModel);
   }
 
@@ -319,6 +346,7 @@
   @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GlonassAssistance implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.location.GlonassAlmanac getAlmanac();
+    method @Nullable public android.location.AuxiliaryInformation getAuxiliaryInformation();
     method @NonNull public java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections> getSatelliteCorrections();
     method @NonNull public java.util.List<android.location.GlonassSatelliteEphemeris> getSatelliteEphemeris();
     method @NonNull public java.util.List<android.location.TimeModel> getTimeModels();
@@ -331,9 +359,10 @@
     ctor public GlonassAssistance.Builder();
     method @NonNull public android.location.GlonassAssistance build();
     method @NonNull public android.location.GlonassAssistance.Builder setAlmanac(@Nullable android.location.GlonassAlmanac);
-    method @NonNull public android.location.GlonassAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
-    method @NonNull public android.location.GlonassAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.GlonassSatelliteEphemeris>);
-    method @NonNull public android.location.GlonassAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>);
+    method @NonNull public android.location.GlonassAssistance.Builder setAuxiliaryInformation(@Nullable android.location.AuxiliaryInformation);
+    method @NonNull public android.location.GlonassAssistance.Builder setSatelliteCorrections(@NonNull java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
+    method @NonNull public android.location.GlonassAssistance.Builder setSatelliteEphemeris(@NonNull java.util.List<android.location.GlonassSatelliteEphemeris>);
+    method @NonNull public android.location.GlonassAssistance.Builder setTimeModels(@NonNull java.util.List<android.location.TimeModel>);
     method @NonNull public android.location.GlonassAssistance.Builder setUtcModel(@Nullable android.location.UtcModel);
   }
 
@@ -688,6 +717,7 @@
   @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GpsAssistance implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.location.GnssAlmanac getAlmanac();
+    method @Nullable public android.location.AuxiliaryInformation getAuxiliaryInformation();
     method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel();
     method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel();
     method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels();
@@ -703,12 +733,13 @@
     ctor public GpsAssistance.Builder();
     method @NonNull public android.location.GpsAssistance build();
     method @NonNull public android.location.GpsAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac);
+    method @NonNull public android.location.GpsAssistance.Builder setAuxiliaryInformation(@Nullable android.location.AuxiliaryInformation);
     method @NonNull public android.location.GpsAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel);
     method @NonNull public android.location.GpsAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel);
-    method @NonNull public android.location.GpsAssistance.Builder setRealTimeIntegrityModels(@Nullable java.util.List<android.location.RealTimeIntegrityModel>);
-    method @NonNull public android.location.GpsAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
-    method @NonNull public android.location.GpsAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.GpsSatelliteEphemeris>);
-    method @NonNull public android.location.GpsAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>);
+    method @NonNull public android.location.GpsAssistance.Builder setRealTimeIntegrityModels(@NonNull java.util.List<android.location.RealTimeIntegrityModel>);
+    method @NonNull public android.location.GpsAssistance.Builder setSatelliteCorrections(@NonNull java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
+    method @NonNull public android.location.GpsAssistance.Builder setSatelliteEphemeris(@NonNull java.util.List<android.location.GpsSatelliteEphemeris>);
+    method @NonNull public android.location.GpsAssistance.Builder setTimeModels(@NonNull java.util.List<android.location.TimeModel>);
     method @NonNull public android.location.GpsAssistance.Builder setUtcModel(@Nullable android.location.UtcModel);
   }
 
@@ -1222,6 +1253,7 @@
   @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class QzssAssistance implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.location.GnssAlmanac getAlmanac();
+    method @Nullable public android.location.AuxiliaryInformation getAuxiliaryInformation();
     method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel();
     method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel();
     method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels();
@@ -1237,12 +1269,13 @@
     ctor public QzssAssistance.Builder();
     method @NonNull public android.location.QzssAssistance build();
     method @NonNull public android.location.QzssAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac);
+    method @NonNull public android.location.QzssAssistance.Builder setAuxiliaryInformation(@Nullable android.location.AuxiliaryInformation);
     method @NonNull public android.location.QzssAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel);
     method @NonNull public android.location.QzssAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel);
-    method @NonNull public android.location.QzssAssistance.Builder setRealTimeIntegrityModels(@Nullable java.util.List<android.location.RealTimeIntegrityModel>);
-    method @NonNull public android.location.QzssAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
-    method @NonNull public android.location.QzssAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.QzssSatelliteEphemeris>);
-    method @NonNull public android.location.QzssAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>);
+    method @NonNull public android.location.QzssAssistance.Builder setRealTimeIntegrityModels(@NonNull java.util.List<android.location.RealTimeIntegrityModel>);
+    method @NonNull public android.location.QzssAssistance.Builder setSatelliteCorrections(@NonNull java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
+    method @NonNull public android.location.QzssAssistance.Builder setSatelliteEphemeris(@NonNull java.util.List<android.location.QzssSatelliteEphemeris>);
+    method @NonNull public android.location.QzssAssistance.Builder setTimeModels(@NonNull java.util.List<android.location.TimeModel>);
     method @NonNull public android.location.QzssAssistance.Builder setUtcModel(@Nullable android.location.UtcModel);
   }
 
@@ -1273,11 +1306,11 @@
     method public int describeContents();
     method @NonNull public String getAdvisoryNumber();
     method @NonNull public String getAdvisoryType();
+    method @NonNull public java.util.List<android.location.GnssSignalType> getBadSignalTypes();
+    method @IntRange(from=1, to=206) public int getBadSvid();
     method @IntRange(from=0) public long getEndDateSeconds();
     method @IntRange(from=0) public long getPublishDateSeconds();
     method @IntRange(from=0) public long getStartDateSeconds();
-    method @IntRange(from=1, to=206) public int getSvid();
-    method public boolean isUsable();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.location.RealTimeIntegrityModel> CREATOR;
   }
@@ -1287,11 +1320,11 @@
     method @NonNull public android.location.RealTimeIntegrityModel build();
     method @NonNull public android.location.RealTimeIntegrityModel.Builder setAdvisoryNumber(@NonNull String);
     method @NonNull public android.location.RealTimeIntegrityModel.Builder setAdvisoryType(@NonNull String);
+    method @NonNull public android.location.RealTimeIntegrityModel.Builder setBadSignalTypes(@NonNull java.util.List<android.location.GnssSignalType>);
+    method @NonNull public android.location.RealTimeIntegrityModel.Builder setBadSvid(@IntRange(from=1, to=206) int);
     method @NonNull public android.location.RealTimeIntegrityModel.Builder setEndDateSeconds(@IntRange(from=0) long);
     method @NonNull public android.location.RealTimeIntegrityModel.Builder setPublishDateSeconds(@IntRange(from=0) long);
     method @NonNull public android.location.RealTimeIntegrityModel.Builder setStartDateSeconds(@IntRange(from=0) long);
-    method @NonNull public android.location.RealTimeIntegrityModel.Builder setSvid(@IntRange(from=1, to=206) int);
-    method @NonNull public android.location.RealTimeIntegrityModel.Builder setUsable(boolean);
   }
 
   @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class SatelliteEphemerisTime implements android.os.Parcelable {
diff --git a/location/java/android/location/AuxiliaryInformation.java b/location/java/android/location/AuxiliaryInformation.java
new file mode 100644
index 0000000..601c87e
--- /dev/null
+++ b/location/java/android/location/AuxiliaryInformation.java
@@ -0,0 +1,274 @@
+/*
+ * 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 android.location;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.location.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A class contains parameters to provide additional assistance information dependent on the GNSS
+ * constellation.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE)
+@SystemApi
+public final class AuxiliaryInformation implements Parcelable {
+
+    /**
+     * BDS B1C Satellite orbit type.
+     *
+     * <p>This is defined in BDS-SIS-ICD-B1I-3.0, section 3.1.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        BDS_B1C_ORBIT_TYPE_UNDEFINED,
+        BDS_B1C_ORBIT_TYPE_GEO,
+        BDS_B1C_ORBIT_TYPE_IGSO,
+        BDS_B1C_ORBIT_TYPE_MEO
+    })
+    public @interface BeidouB1CSatelliteOrbitType {}
+
+    /**
+     * The following enumerations must be in sync with the values declared in
+     * AuxiliaryInformation.aidl.
+     */
+
+    /** The orbit type is undefined. */
+    public static final int BDS_B1C_ORBIT_TYPE_UNDEFINED = 0;
+
+    /** The orbit type is GEO. */
+    public static final int BDS_B1C_ORBIT_TYPE_GEO = 1;
+
+    /** The orbit type is IGSO. */
+    public static final int BDS_B1C_ORBIT_TYPE_IGSO = 2;
+
+    /** The orbit type is MEO. */
+    public static final int BDS_B1C_ORBIT_TYPE_MEO = 3;
+
+    /**
+     * Pseudo-random or satellite ID number for the satellite, a.k.a. Space Vehicle (SV), or OSN
+     * number for Glonass.
+     *
+     * <p>The distinction is made by looking at the constellation field. Values must be in the range
+     * of:
+     *
+     * <p>- GPS: 1-32
+     *
+     * <p>- GLONASS: 1-25
+     *
+     * <p>- QZSS: 183-206
+     *
+     * <p>- Galileo: 1-36
+     *
+     * <p>- Beidou: 1-63
+     */
+    private final int mSvid;
+
+    /** The list of available signal types for the satellite. */
+    @NonNull private final List<GnssSignalType> mAvailableSignalTypes;
+
+    /**
+     * Glonass carrier frequency number of the satellite. This is required for Glonass.
+     *
+     * <p>This is defined in Glonass ICD v5.1 section 3.3.1.1.
+     */
+    private final int mFrequencyChannelNumber;
+
+    /** BDS B1C satellite orbit type. This is required for Beidou. */
+    private final @BeidouB1CSatelliteOrbitType int mSatType;
+
+    private AuxiliaryInformation(Builder builder) {
+        // Allow Svid beyond the range to support potential future extensibility.
+        Preconditions.checkArgument(builder.mSvid >= 1);
+        Preconditions.checkNotNull(
+                builder.mAvailableSignalTypes, "AvailableSignalTypes cannot be null");
+        Preconditions.checkArgument(builder.mAvailableSignalTypes.size() > 0);
+        Preconditions.checkArgumentInRange(
+                builder.mFrequencyChannelNumber, -7, 6, "FrequencyChannelNumber");
+        Preconditions.checkArgumentInRange(
+                builder.mSatType, BDS_B1C_ORBIT_TYPE_UNDEFINED, BDS_B1C_ORBIT_TYPE_MEO, "SatType");
+        mSvid = builder.mSvid;
+        mAvailableSignalTypes =
+                Collections.unmodifiableList(new ArrayList<>(builder.mAvailableSignalTypes));
+        mFrequencyChannelNumber = builder.mFrequencyChannelNumber;
+        mSatType = builder.mSatType;
+    }
+
+    /**
+     * Returns the Pseudo-random or satellite ID number for the satellite, a.k.a. Space Vehicle
+     * (SV), or OSN number for Glonass.
+     *
+     * <p>The distinction is made by looking at the constellation field. Values must be in the range
+     * of:
+     *
+     * <p>- GPS: 1-32
+     *
+     * <p>- GLONASS: 1-25
+     *
+     * <p>- QZSS: 183-206
+     *
+     * <p>- Galileo: 1-36
+     *
+     * <p>- Beidou: 1-63
+     */
+    @IntRange(from = 1)
+    public int getSvid() {
+        return mSvid;
+    }
+
+    /** Returns the list of available signal types for the satellite. */
+    @NonNull
+    public List<GnssSignalType> getAvailableSignalTypes() {
+        return mAvailableSignalTypes;
+    }
+
+    /** Returns the Glonass carrier frequency number of the satellite. */
+    @IntRange(from = -7, to = 6)
+    public int getFrequencyChannelNumber() {
+        return mFrequencyChannelNumber;
+    }
+
+    /** Returns the BDS B1C satellite orbit type. */
+    @BeidouB1CSatelliteOrbitType
+    public int getSatType() {
+        return mSatType;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mSvid);
+        dest.writeTypedList(mAvailableSignalTypes);
+        dest.writeInt(mFrequencyChannelNumber);
+        dest.writeInt(mSatType);
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        StringBuilder builder = new StringBuilder("AuxiliaryInformation[");
+        builder.append("svid = ").append(mSvid);
+        builder.append(", availableSignalTypes = ").append(mAvailableSignalTypes);
+        builder.append(", frequencyChannelNumber = ").append(mFrequencyChannelNumber);
+        builder.append(", satType = ").append(mSatType);
+        builder.append("]");
+        return builder.toString();
+    }
+
+    public static final @NonNull Parcelable.Creator<AuxiliaryInformation> CREATOR =
+            new Parcelable.Creator<AuxiliaryInformation>() {
+                @Override
+                public AuxiliaryInformation createFromParcel(@NonNull Parcel in) {
+                    return new AuxiliaryInformation.Builder()
+                            .setSvid(in.readInt())
+                            .setAvailableSignalTypes(
+                                    in.createTypedArrayList(GnssSignalType.CREATOR))
+                            .setFrequencyChannelNumber(in.readInt())
+                            .setSatType(in.readInt())
+                            .build();
+                }
+
+                @Override
+                public AuxiliaryInformation[] newArray(int size) {
+                    return new AuxiliaryInformation[size];
+                }
+            };
+
+    /** A builder class for {@link AuxiliaryInformation}. */
+    public static final class Builder {
+        private int mSvid;
+        private List<GnssSignalType> mAvailableSignalTypes;
+        private int mFrequencyChannelNumber;
+        private @BeidouB1CSatelliteOrbitType int mSatType;
+
+        /**
+         * Sets the Pseudo-random or satellite ID number for the satellite, a.k.a. Space Vehicle
+         * (SV), or OSN number for Glonass.
+         *
+         * <p>The distinction is made by looking at the constellation field. Values must be in the
+         * range of:
+         *
+         * <p>- GPS: 1-32
+         *
+         * <p>- GLONASS: 1-25
+         *
+         * <p>- QZSS: 183-206
+         *
+         * <p>- Galileo: 1-36
+         *
+         * <p>- Beidou: 1-63
+         */
+        @NonNull
+        public Builder setSvid(@IntRange(from = 1) int svid) {
+            mSvid = svid;
+            return this;
+        }
+
+        /**
+         * Sets the list of available signal types for the satellite.
+         *
+         * <p>The list must be set and cannot be an empty list.
+         */
+        @NonNull
+        public Builder setAvailableSignalTypes(@NonNull List<GnssSignalType> availableSignalTypes) {
+            mAvailableSignalTypes = availableSignalTypes;
+            return this;
+        }
+
+        /** Sets the Glonass carrier frequency number of the satellite. */
+        @NonNull
+        public Builder setFrequencyChannelNumber(
+                @IntRange(from = -7, to = 6) int frequencyChannelNumber) {
+            mFrequencyChannelNumber = frequencyChannelNumber;
+            return this;
+        }
+
+        /** Sets the BDS B1C satellite orbit type. */
+        @NonNull
+        public Builder setSatType(@BeidouB1CSatelliteOrbitType int satType) {
+            mSatType = satType;
+            return this;
+        }
+
+        /** Builds a {@link AuxiliaryInformation} instance as specified by this builder. */
+        @NonNull
+        public AuxiliaryInformation build() {
+            return new AuxiliaryInformation(this);
+        }
+    }
+}
diff --git a/location/java/android/location/BeidouAssistance.java b/location/java/android/location/BeidouAssistance.java
index f55249e6..e35493e 100644
--- a/location/java/android/location/BeidouAssistance.java
+++ b/location/java/android/location/BeidouAssistance.java
@@ -19,7 +19,6 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.location.GnssAssistance.GnssSatelliteCorrections;
 import android.location.flags.Flags;
@@ -51,6 +50,9 @@
     /** The leap seconds model. */
     @Nullable private final LeapSecondsModel mLeapSecondsModel;
 
+    /** The auxiliary information. */
+    @Nullable private final AuxiliaryInformation mAuxiliaryInformation;
+
     /** The list of time models. */
     @NonNull private final List<TimeModel> mTimeModels;
 
@@ -68,6 +70,7 @@
         mIonosphericModel = builder.mIonosphericModel;
         mUtcModel = builder.mUtcModel;
         mLeapSecondsModel = builder.mLeapSecondsModel;
+        mAuxiliaryInformation = builder.mAuxiliaryInformation;
         if (builder.mTimeModels != null) {
             mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels));
         } else {
@@ -117,6 +120,12 @@
         return mLeapSecondsModel;
     }
 
+    /** Returns the auxiliary information. */
+    @Nullable
+    public AuxiliaryInformation getAuxiliaryInformation() {
+        return mAuxiliaryInformation;
+    }
+
     /** Returns the list of time models. */
     @NonNull
     public List<TimeModel> getTimeModels() {
@@ -154,6 +163,7 @@
         builder.append(", ionosphericModel = ").append(mIonosphericModel);
         builder.append(", utcModel = ").append(mUtcModel);
         builder.append(", leapSecondsModel = ").append(mLeapSecondsModel);
+        builder.append(", auxiliaryInformation = ").append(mAuxiliaryInformation);
         builder.append(", timeModels = ").append(mTimeModels);
         builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris);
         builder.append(", realTimeIntegrityModels = ").append(mRealTimeIntegrityModels);
@@ -168,6 +178,7 @@
         dest.writeTypedObject(mIonosphericModel, flags);
         dest.writeTypedObject(mUtcModel, flags);
         dest.writeTypedObject(mLeapSecondsModel, flags);
+        dest.writeTypedObject(mAuxiliaryInformation, flags);
         dest.writeTypedList(mTimeModels);
         dest.writeTypedList(mSatelliteEphemeris);
         dest.writeTypedList(mRealTimeIntegrityModels);
@@ -184,6 +195,8 @@
                                     in.readTypedObject(KlobucharIonosphericModel.CREATOR))
                             .setUtcModel(in.readTypedObject(UtcModel.CREATOR))
                             .setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR))
+                            .setAuxiliaryInformation(
+                                    in.readTypedObject(AuxiliaryInformation.CREATOR))
                             .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR))
                             .setSatelliteEphemeris(
                                     in.createTypedArrayList(BeidouSatelliteEphemeris.CREATOR))
@@ -206,6 +219,7 @@
         private KlobucharIonosphericModel mIonosphericModel;
         private UtcModel mUtcModel;
         private LeapSecondsModel mLeapSecondsModel;
+        private AuxiliaryInformation mAuxiliaryInformation;
         private List<TimeModel> mTimeModels;
         private List<BeidouSatelliteEphemeris> mSatelliteEphemeris;
         private List<RealTimeIntegrityModel> mRealTimeIntegrityModels;
@@ -239,10 +253,17 @@
             return this;
         }
 
+        /** Sets the auxiliary information. */
+        @NonNull
+        public Builder setAuxiliaryInformation(
+                @Nullable AuxiliaryInformation auxiliaryInformation) {
+            mAuxiliaryInformation = auxiliaryInformation;
+            return this;
+        }
+
         /** Sets the list of time models. */
         @NonNull
-        public Builder setTimeModels(
-                @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) {
+        public Builder setTimeModels(@NonNull List<TimeModel> timeModels) {
             mTimeModels = timeModels;
             return this;
         }
@@ -250,8 +271,7 @@
         /** Sets the list of Beidou ephemeris. */
         @NonNull
         public Builder setSatelliteEphemeris(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<BeidouSatelliteEphemeris> satelliteEphemeris) {
+                @NonNull List<BeidouSatelliteEphemeris> satelliteEphemeris) {
             mSatelliteEphemeris = satelliteEphemeris;
             return this;
         }
@@ -259,8 +279,7 @@
         /** Sets the list of real time integrity models. */
         @NonNull
         public Builder setRealTimeIntegrityModels(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<RealTimeIntegrityModel> realTimeIntegrityModels) {
+                @NonNull List<RealTimeIntegrityModel> realTimeIntegrityModels) {
             mRealTimeIntegrityModels = realTimeIntegrityModels;
             return this;
         }
@@ -268,8 +287,7 @@
         /** Sets the list of Beidou satellite corrections. */
         @NonNull
         public Builder setSatelliteCorrections(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<GnssSatelliteCorrections> satelliteCorrections) {
+                @NonNull List<GnssSatelliteCorrections> satelliteCorrections) {
             mSatelliteCorrections = satelliteCorrections;
             return this;
         }
diff --git a/location/java/android/location/GalileoAssistance.java b/location/java/android/location/GalileoAssistance.java
index 07c5bab..8a09e66 100644
--- a/location/java/android/location/GalileoAssistance.java
+++ b/location/java/android/location/GalileoAssistance.java
@@ -19,7 +19,6 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.location.GnssAssistance.GnssSatelliteCorrections;
 import android.location.flags.Flags;
@@ -51,6 +50,9 @@
     /** The leap seconds model. */
     @Nullable private final LeapSecondsModel mLeapSecondsModel;
 
+    /** The auxiliary information. */
+    @Nullable private final AuxiliaryInformation mAuxiliaryInformation;
+
     /** The list of time models. */
     @NonNull private final List<TimeModel> mTimeModels;
 
@@ -68,6 +70,7 @@
         mIonosphericModel = builder.mIonosphericModel;
         mUtcModel = builder.mUtcModel;
         mLeapSecondsModel = builder.mLeapSecondsModel;
+        mAuxiliaryInformation = builder.mAuxiliaryInformation;
         if (builder.mTimeModels != null) {
             mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels));
         } else {
@@ -117,6 +120,12 @@
         return mLeapSecondsModel;
     }
 
+    /** Returns the auxiliary information. */
+    @Nullable
+    public AuxiliaryInformation getAuxiliaryInformation() {
+        return mAuxiliaryInformation;
+    }
+
     /** Returns the list of time models. */
     @NonNull
     public List<TimeModel> getTimeModels() {
@@ -152,6 +161,7 @@
         dest.writeTypedObject(mIonosphericModel, flags);
         dest.writeTypedObject(mUtcModel, flags);
         dest.writeTypedObject(mLeapSecondsModel, flags);
+        dest.writeTypedObject(mAuxiliaryInformation, flags);
         dest.writeTypedList(mTimeModels);
         dest.writeTypedList(mSatelliteEphemeris);
         dest.writeTypedList(mRealTimeIntegrityModels);
@@ -166,6 +176,7 @@
         builder.append(", ionosphericModel = ").append(mIonosphericModel);
         builder.append(", utcModel = ").append(mUtcModel);
         builder.append(", leapSecondsModel = ").append(mLeapSecondsModel);
+        builder.append(", auxiliaryInformation = ").append(mAuxiliaryInformation);
         builder.append(", timeModels = ").append(mTimeModels);
         builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris);
         builder.append(", realTimeIntegrityModels = ").append(mRealTimeIntegrityModels);
@@ -184,6 +195,8 @@
                                     in.readTypedObject(KlobucharIonosphericModel.CREATOR))
                             .setUtcModel(in.readTypedObject(UtcModel.CREATOR))
                             .setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR))
+                            .setAuxiliaryInformation(
+                                    in.readTypedObject(AuxiliaryInformation.CREATOR))
                             .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR))
                             .setSatelliteEphemeris(
                                     in.createTypedArrayList(GalileoSatelliteEphemeris.CREATOR))
@@ -206,6 +219,7 @@
         private KlobucharIonosphericModel mIonosphericModel;
         private UtcModel mUtcModel;
         private LeapSecondsModel mLeapSecondsModel;
+        private AuxiliaryInformation mAuxiliaryInformation;
         private List<TimeModel> mTimeModels;
         private List<GalileoSatelliteEphemeris> mSatelliteEphemeris;
         private List<RealTimeIntegrityModel> mRealTimeIntegrityModels;
@@ -239,10 +253,17 @@
             return this;
         }
 
+        /** Sets the auxiliary information. */
+        @NonNull
+        public Builder setAuxiliaryInformation(
+                @Nullable AuxiliaryInformation auxiliaryInformation) {
+            mAuxiliaryInformation = auxiliaryInformation;
+            return this;
+        }
+
         /** Sets the list of time models. */
         @NonNull
-        public Builder setTimeModels(
-                @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) {
+        public Builder setTimeModels(@NonNull List<TimeModel> timeModels) {
             mTimeModels = timeModels;
             return this;
         }
@@ -250,8 +271,7 @@
         /** Sets the list of Galileo ephemeris. */
         @NonNull
         public Builder setSatelliteEphemeris(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<GalileoSatelliteEphemeris> satelliteEphemeris) {
+                @NonNull List<GalileoSatelliteEphemeris> satelliteEphemeris) {
             mSatelliteEphemeris = satelliteEphemeris;
             return this;
         }
@@ -259,8 +279,7 @@
         /** Sets the list of real time integrity models. */
         @NonNull
         public Builder setRealTimeIntegrityModels(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<RealTimeIntegrityModel> realTimeIntegrityModels) {
+                @NonNull List<RealTimeIntegrityModel> realTimeIntegrityModels) {
             mRealTimeIntegrityModels = realTimeIntegrityModels;
             return this;
         }
@@ -268,8 +287,7 @@
         /** Sets the list of Galileo satellite corrections. */
         @NonNull
         public Builder setSatelliteCorrections(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<GnssSatelliteCorrections> satelliteCorrections) {
+                @NonNull List<GnssSatelliteCorrections> satelliteCorrections) {
             mSatelliteCorrections = satelliteCorrections;
             return this;
         }
diff --git a/location/java/android/location/GlonassAssistance.java b/location/java/android/location/GlonassAssistance.java
index cc08201..c7ed1c5 100644
--- a/location/java/android/location/GlonassAssistance.java
+++ b/location/java/android/location/GlonassAssistance.java
@@ -19,7 +19,6 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.location.GnssAssistance.GnssSatelliteCorrections;
 import android.location.flags.Flags;
@@ -45,6 +44,9 @@
     /** The UTC model. */
     @Nullable private final UtcModel mUtcModel;
 
+    /** The auxiliary information. */
+    @Nullable private final AuxiliaryInformation mAuxiliaryInformation;
+
     /** The list of time models. */
     @NonNull private final List<TimeModel> mTimeModels;
 
@@ -57,6 +59,7 @@
     private GlonassAssistance(Builder builder) {
         mAlmanac = builder.mAlmanac;
         mUtcModel = builder.mUtcModel;
+        mAuxiliaryInformation = builder.mAuxiliaryInformation;
         if (builder.mTimeModels != null) {
             mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels));
         } else {
@@ -106,6 +109,12 @@
         return mSatelliteCorrections;
     }
 
+    /** Returns the auxiliary information. */
+    @Nullable
+    public AuxiliaryInformation getAuxiliaryInformation() {
+        return mAuxiliaryInformation;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -115,6 +124,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeTypedObject(mAlmanac, flags);
         dest.writeTypedObject(mUtcModel, flags);
+        dest.writeTypedObject(mAuxiliaryInformation, flags);
         dest.writeTypedList(mTimeModels);
         dest.writeTypedList(mSatelliteEphemeris);
         dest.writeTypedList(mSatelliteCorrections);
@@ -126,6 +136,7 @@
         StringBuilder builder = new StringBuilder("GlonassAssistance[");
         builder.append("almanac = ").append(mAlmanac);
         builder.append(", utcModel = ").append(mUtcModel);
+        builder.append(", auxiliaryInformation = ").append(mAuxiliaryInformation);
         builder.append(", timeModels = ").append(mTimeModels);
         builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris);
         builder.append(", satelliteCorrections = ").append(mSatelliteCorrections);
@@ -140,6 +151,8 @@
                     return new GlonassAssistance.Builder()
                             .setAlmanac(in.readTypedObject(GlonassAlmanac.CREATOR))
                             .setUtcModel(in.readTypedObject(UtcModel.CREATOR))
+                            .setAuxiliaryInformation(
+                                    in.readTypedObject(AuxiliaryInformation.CREATOR))
                             .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR))
                             .setSatelliteEphemeris(
                                     in.createTypedArrayList(GlonassSatelliteEphemeris.CREATOR))
@@ -158,30 +171,36 @@
     public static final class Builder {
         private GlonassAlmanac mAlmanac;
         private UtcModel mUtcModel;
+        private AuxiliaryInformation mAuxiliaryInformation;
         private List<TimeModel> mTimeModels;
         private List<GlonassSatelliteEphemeris> mSatelliteEphemeris;
         private List<GnssSatelliteCorrections> mSatelliteCorrections;
 
         /** Sets the Glonass almanac. */
         @NonNull
-        public Builder setAlmanac(
-                @Nullable @SuppressLint("NullableCollection") GlonassAlmanac almanac) {
+        public Builder setAlmanac(@Nullable GlonassAlmanac almanac) {
             mAlmanac = almanac;
             return this;
         }
 
         /** Sets the UTC model. */
         @NonNull
-        public Builder setUtcModel(
-                @Nullable @SuppressLint("NullableCollection") UtcModel utcModel) {
+        public Builder setUtcModel(@Nullable UtcModel utcModel) {
             mUtcModel = utcModel;
             return this;
         }
 
+        /** Sets the auxiliary information. */
+        @NonNull
+        public Builder setAuxiliaryInformation(
+                @Nullable AuxiliaryInformation auxiliaryInformation) {
+            mAuxiliaryInformation = auxiliaryInformation;
+            return this;
+        }
+
         /** Sets the list of time models. */
         @NonNull
-        public Builder setTimeModels(
-                @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) {
+        public Builder setTimeModels(@NonNull List<TimeModel> timeModels) {
             mTimeModels = timeModels;
             return this;
         }
@@ -189,8 +208,7 @@
         /** Sets the list of Glonass satellite ephemeris. */
         @NonNull
         public Builder setSatelliteEphemeris(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<GlonassSatelliteEphemeris> satelliteEphemeris) {
+                @NonNull List<GlonassSatelliteEphemeris> satelliteEphemeris) {
             mSatelliteEphemeris = satelliteEphemeris;
             return this;
         }
@@ -198,8 +216,7 @@
         /** Sets the list of Glonass satellite corrections. */
         @NonNull
         public Builder setSatelliteCorrections(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<GnssSatelliteCorrections> satelliteCorrections) {
+                @NonNull List<GnssSatelliteCorrections> satelliteCorrections) {
             mSatelliteCorrections = satelliteCorrections;
             return this;
         }
diff --git a/location/java/android/location/GpsAssistance.java b/location/java/android/location/GpsAssistance.java
index 5202fc4..5a8802f 100644
--- a/location/java/android/location/GpsAssistance.java
+++ b/location/java/android/location/GpsAssistance.java
@@ -51,6 +51,9 @@
     /** The leap seconds model. */
     @Nullable private final LeapSecondsModel mLeapSecondsModel;
 
+    /** The auxiliary information. */
+    @Nullable private final AuxiliaryInformation mAuxiliaryInformation;
+
     /** The list of time models. */
     @NonNull private final List<TimeModel> mTimeModels;
 
@@ -68,6 +71,7 @@
         mIonosphericModel = builder.mIonosphericModel;
         mUtcModel = builder.mUtcModel;
         mLeapSecondsModel = builder.mLeapSecondsModel;
+        mAuxiliaryInformation = builder.mAuxiliaryInformation;
         if (builder.mTimeModels != null) {
             mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels));
         } else {
@@ -117,6 +121,12 @@
         return mLeapSecondsModel;
     }
 
+    /** Returns the auxiliary information. */
+    @Nullable
+    public AuxiliaryInformation getAuxiliaryInformation() {
+        return mAuxiliaryInformation;
+    }
+
     /** Returns the list of time models. */
     @NonNull
     public List<TimeModel> getTimeModels() {
@@ -152,6 +162,8 @@
                                     in.readTypedObject(KlobucharIonosphericModel.CREATOR))
                             .setUtcModel(in.readTypedObject(UtcModel.CREATOR))
                             .setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR))
+                            .setAuxiliaryInformation(
+                                    in.readTypedObject(AuxiliaryInformation.CREATOR))
                             .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR))
                             .setSatelliteEphemeris(
                                     in.createTypedArrayList(GpsSatelliteEphemeris.CREATOR))
@@ -179,6 +191,7 @@
         dest.writeTypedObject(mIonosphericModel, flags);
         dest.writeTypedObject(mUtcModel, flags);
         dest.writeTypedObject(mLeapSecondsModel, flags);
+        dest.writeTypedObject(mAuxiliaryInformation, flags);
         dest.writeTypedList(mTimeModels);
         dest.writeTypedList(mSatelliteEphemeris);
         dest.writeTypedList(mRealTimeIntegrityModels);
@@ -193,6 +206,7 @@
         builder.append(", ionosphericModel = ").append(mIonosphericModel);
         builder.append(", utcModel = ").append(mUtcModel);
         builder.append(", leapSecondsModel = ").append(mLeapSecondsModel);
+        builder.append(", auxiliaryInformation = ").append(mAuxiliaryInformation);
         builder.append(", timeModels = ").append(mTimeModels);
         builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris);
         builder.append(", realTimeIntegrityModels = ").append(mRealTimeIntegrityModels);
@@ -207,6 +221,7 @@
         private KlobucharIonosphericModel mIonosphericModel;
         private UtcModel mUtcModel;
         private LeapSecondsModel mLeapSecondsModel;
+        private AuxiliaryInformation mAuxiliaryInformation;
         private List<TimeModel> mTimeModels;
         private List<GpsSatelliteEphemeris> mSatelliteEphemeris;
         private List<RealTimeIntegrityModel> mRealTimeIntegrityModels;
@@ -222,33 +237,36 @@
 
         /** Sets the Klobuchar ionospheric model. */
         @NonNull
-        public Builder setIonosphericModel(
-                @Nullable @SuppressLint("NullableCollection")
-                        KlobucharIonosphericModel ionosphericModel) {
+        public Builder setIonosphericModel(@Nullable KlobucharIonosphericModel ionosphericModel) {
             mIonosphericModel = ionosphericModel;
             return this;
         }
 
         /** Sets the UTC model. */
         @NonNull
-        public Builder setUtcModel(
-                @Nullable @SuppressLint("NullableCollection") UtcModel utcModel) {
+        public Builder setUtcModel(@Nullable UtcModel utcModel) {
             mUtcModel = utcModel;
             return this;
         }
 
         /** Sets the leap seconds model. */
         @NonNull
-        public Builder setLeapSecondsModel(
-                @Nullable @SuppressLint("NullableCollection") LeapSecondsModel leapSecondsModel) {
+        public Builder setLeapSecondsModel(@Nullable LeapSecondsModel leapSecondsModel) {
             mLeapSecondsModel = leapSecondsModel;
             return this;
         }
 
+        /** Sets the auxiliary information. */
+        @NonNull
+        public Builder setAuxiliaryInformation(
+                @Nullable AuxiliaryInformation auxiliaryInformation) {
+            mAuxiliaryInformation = auxiliaryInformation;
+            return this;
+        }
+
         /** Sets the list of time models. */
         @NonNull
-        public Builder setTimeModels(
-                @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) {
+        public Builder setTimeModels(@NonNull List<TimeModel> timeModels) {
             mTimeModels = timeModels;
             return this;
         }
@@ -256,8 +274,7 @@
         /** Sets the list of GPS ephemeris. */
         @NonNull
         public Builder setSatelliteEphemeris(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<GpsSatelliteEphemeris> satelliteEphemeris) {
+                @NonNull List<GpsSatelliteEphemeris> satelliteEphemeris) {
             mSatelliteEphemeris = satelliteEphemeris;
             return this;
         }
@@ -265,8 +282,7 @@
         /** Sets the list of real time integrity models. */
         @NonNull
         public Builder setRealTimeIntegrityModels(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<RealTimeIntegrityModel> realTimeIntegrityModels) {
+                @NonNull List<RealTimeIntegrityModel> realTimeIntegrityModels) {
             mRealTimeIntegrityModels = realTimeIntegrityModels;
             return this;
         }
@@ -274,8 +290,7 @@
         /** Sets the list of GPS satellite corrections. */
         @NonNull
         public Builder setSatelliteCorrections(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<GnssSatelliteCorrections> satelliteCorrections) {
+                @NonNull List<GnssSatelliteCorrections> satelliteCorrections) {
             mSatelliteCorrections = satelliteCorrections;
             return this;
         }
diff --git a/location/java/android/location/QzssAssistance.java b/location/java/android/location/QzssAssistance.java
index 9383ce3..27c3437 100644
--- a/location/java/android/location/QzssAssistance.java
+++ b/location/java/android/location/QzssAssistance.java
@@ -19,7 +19,6 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.location.GnssAssistance.GnssSatelliteCorrections;
 import android.location.flags.Flags;
@@ -51,6 +50,9 @@
     /** The leap seconds model. */
     @Nullable private final LeapSecondsModel mLeapSecondsModel;
 
+    /** The auxiliary information. */
+    @Nullable private final AuxiliaryInformation mAuxiliaryInformation;
+
     /** The list of time models. */
     @NonNull private final List<TimeModel> mTimeModels;
 
@@ -68,6 +70,7 @@
         mIonosphericModel = builder.mIonosphericModel;
         mUtcModel = builder.mUtcModel;
         mLeapSecondsModel = builder.mLeapSecondsModel;
+        mAuxiliaryInformation = builder.mAuxiliaryInformation;
         if (builder.mTimeModels != null) {
             mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels));
         } else {
@@ -117,6 +120,12 @@
         return mLeapSecondsModel;
     }
 
+    /** Returns the auxiliary information. */
+    @Nullable
+    public AuxiliaryInformation getAuxiliaryInformation() {
+        return mAuxiliaryInformation;
+    }
+
     /** Returns the list of time models. */
     @NonNull
     public List<TimeModel> getTimeModels() {
@@ -147,19 +156,23 @@
                 @NonNull
                 public QzssAssistance createFromParcel(Parcel in) {
                     return new QzssAssistance.Builder()
-                        .setAlmanac(in.readTypedObject(GnssAlmanac.CREATOR))
-                        .setIonosphericModel(in.readTypedObject(KlobucharIonosphericModel.CREATOR))
-                        .setUtcModel(in.readTypedObject(UtcModel.CREATOR))
-                        .setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR))
-                        .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR))
-                        .setSatelliteEphemeris(
-                                in.createTypedArrayList(QzssSatelliteEphemeris.CREATOR))
-                        .setRealTimeIntegrityModels(
-                                in.createTypedArrayList(RealTimeIntegrityModel.CREATOR))
-                        .setSatelliteCorrections(
-                                in.createTypedArrayList(GnssSatelliteCorrections.CREATOR))
-                        .build();
+                            .setAlmanac(in.readTypedObject(GnssAlmanac.CREATOR))
+                            .setIonosphericModel(
+                                    in.readTypedObject(KlobucharIonosphericModel.CREATOR))
+                            .setUtcModel(in.readTypedObject(UtcModel.CREATOR))
+                            .setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR))
+                            .setAuxiliaryInformation(
+                                    in.readTypedObject(AuxiliaryInformation.CREATOR))
+                            .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR))
+                            .setSatelliteEphemeris(
+                                    in.createTypedArrayList(QzssSatelliteEphemeris.CREATOR))
+                            .setRealTimeIntegrityModels(
+                                    in.createTypedArrayList(RealTimeIntegrityModel.CREATOR))
+                            .setSatelliteCorrections(
+                                    in.createTypedArrayList(GnssSatelliteCorrections.CREATOR))
+                            .build();
                 }
+
                 @Override
                 public QzssAssistance[] newArray(int size) {
                     return new QzssAssistance[size];
@@ -177,6 +190,7 @@
         dest.writeTypedObject(mIonosphericModel, flags);
         dest.writeTypedObject(mUtcModel, flags);
         dest.writeTypedObject(mLeapSecondsModel, flags);
+        dest.writeTypedObject(mAuxiliaryInformation, flags);
         dest.writeTypedList(mTimeModels);
         dest.writeTypedList(mSatelliteEphemeris);
         dest.writeTypedList(mRealTimeIntegrityModels);
@@ -191,6 +205,7 @@
         builder.append(", ionosphericModel = ").append(mIonosphericModel);
         builder.append(", utcModel = ").append(mUtcModel);
         builder.append(", leapSecondsModel = ").append(mLeapSecondsModel);
+        builder.append(", auxiliaryInformation = ").append(mAuxiliaryInformation);
         builder.append(", timeModels = ").append(mTimeModels);
         builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris);
         builder.append(", realTimeIntegrityModels = ").append(mRealTimeIntegrityModels);
@@ -205,6 +220,7 @@
         private KlobucharIonosphericModel mIonosphericModel;
         private UtcModel mUtcModel;
         private LeapSecondsModel mLeapSecondsModel;
+        private AuxiliaryInformation mAuxiliaryInformation;
         private List<TimeModel> mTimeModels;
         private List<QzssSatelliteEphemeris> mSatelliteEphemeris;
         private List<RealTimeIntegrityModel> mRealTimeIntegrityModels;
@@ -238,10 +254,17 @@
             return this;
         }
 
+        /** Sets the auxiliary information. */
+        @NonNull
+        public Builder setAuxiliaryInformation(
+                @Nullable AuxiliaryInformation auxiliaryInformation) {
+            mAuxiliaryInformation = auxiliaryInformation;
+            return this;
+        }
+
         /** Sets the list of time models. */
         @NonNull
-        public Builder setTimeModels(
-                @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) {
+        public Builder setTimeModels(@NonNull List<TimeModel> timeModels) {
             mTimeModels = timeModels;
             return this;
         }
@@ -249,8 +272,7 @@
         /** Sets the list of QZSS ephemeris. */
         @NonNull
         public Builder setSatelliteEphemeris(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<QzssSatelliteEphemeris> satelliteEphemeris) {
+                @NonNull List<QzssSatelliteEphemeris> satelliteEphemeris) {
             mSatelliteEphemeris = satelliteEphemeris;
             return this;
         }
@@ -258,8 +280,7 @@
         /** Sets the list of real time integrity model. */
         @NonNull
         public Builder setRealTimeIntegrityModels(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<RealTimeIntegrityModel> realTimeIntegrityModels) {
+                @NonNull List<RealTimeIntegrityModel> realTimeIntegrityModels) {
             mRealTimeIntegrityModels = realTimeIntegrityModels;
             return this;
         }
@@ -267,8 +288,7 @@
         /** Sets the list of QZSS satellite correction. */
         @NonNull
         public Builder setSatelliteCorrections(
-                @Nullable @SuppressLint("NullableCollection")
-                        List<GnssSatelliteCorrections> satelliteCorrections) {
+                @NonNull List<GnssSatelliteCorrections> satelliteCorrections) {
             mSatelliteCorrections = satelliteCorrections;
             return this;
         }
diff --git a/location/java/android/location/RealTimeIntegrityModel.java b/location/java/android/location/RealTimeIntegrityModel.java
index d268926..f065def 100644
--- a/location/java/android/location/RealTimeIntegrityModel.java
+++ b/location/java/android/location/RealTimeIntegrityModel.java
@@ -26,6 +26,10 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 /**
  * A class contains the real time integrity status of a GNSS satellite based on notice advisory.
  *
@@ -35,8 +39,7 @@
 @SystemApi
 public final class RealTimeIntegrityModel implements Parcelable {
     /**
-     * Pseudo-random or satellite ID number for the satellite,
-     * a.k.a. Space Vehicle (SV), or OSN number for Glonass.
+     * Bad satellite ID number or OSN number for Glonass.
      *
      * <p>The distinction is made by looking at the constellation field. Values
      * must be in the range of:
@@ -47,10 +50,14 @@
      * <p> - Galileo: 1-36
      * <p> - Beidou: 1-63
      */
-    private final int mSvid;
+    private final int mBadSvid;
 
-    /** Indicates whether the satellite is currently usable for navigation. */
-    private final boolean mUsable;
+    /**
+     * The type of the bad signal or signals.
+     *
+     * <p>An empty list means that all signals on the specific SV are not healthy.
+     */
+    @NonNull private final List<GnssSignalType> mBadSignalTypes;
 
     /** UTC timestamp (in seconds) when the advisory was published. */
     private final long mPublishDateSeconds;
@@ -81,14 +88,19 @@
 
     private RealTimeIntegrityModel(Builder builder) {
         // Allow SV ID beyond the range to support potential future extensibility.
-        Preconditions.checkArgument(builder.mSvid >= 1);
+        Preconditions.checkArgument(builder.mBadSvid >= 1);
         Preconditions.checkArgument(builder.mPublishDateSeconds > 0);
         Preconditions.checkArgument(builder.mStartDateSeconds > 0);
         Preconditions.checkArgument(builder.mEndDateSeconds > 0);
         Preconditions.checkNotNull(builder.mAdvisoryType, "AdvisoryType cannot be null");
         Preconditions.checkNotNull(builder.mAdvisoryNumber, "AdvisoryNumber cannot be null");
-        mSvid = builder.mSvid;
-        mUsable = builder.mUsable;
+        if (builder.mBadSignalTypes == null) {
+            mBadSignalTypes = new ArrayList<>();
+        } else {
+            mBadSignalTypes = Collections.unmodifiableList(
+                new ArrayList<>(builder.mBadSignalTypes));
+        }
+        mBadSvid = builder.mBadSvid;
         mPublishDateSeconds = builder.mPublishDateSeconds;
         mStartDateSeconds = builder.mStartDateSeconds;
         mEndDateSeconds = builder.mEndDateSeconds;
@@ -110,13 +122,18 @@
      * <p> - Beidou: 1-63
      */
     @IntRange(from = 1, to = 206)
-    public int getSvid() {
-        return mSvid;
+    public int getBadSvid() {
+        return mBadSvid;
     }
 
-    /** Returns whether the satellite is usable or not. */
-    public boolean isUsable() {
-        return mUsable;
+    /**
+     * Returns the type of the bad signal or signals.
+     *
+     * <p>An empty list means that all signals on the specific SV are not healthy.
+     */
+    @NonNull
+    public List<GnssSignalType> getBadSignalTypes() {
+        return mBadSignalTypes;
     }
 
     /** Returns the UTC timestamp (in seconds) when the advisory was published */
@@ -156,8 +173,9 @@
                 public RealTimeIntegrityModel createFromParcel(Parcel in) {
                     RealTimeIntegrityModel realTimeIntegrityModel =
                             new RealTimeIntegrityModel.Builder()
-                                    .setSvid(in.readInt())
-                                    .setUsable(in.readBoolean())
+                                    .setBadSvid(in.readInt())
+                                    .setBadSignalTypes(
+                                      in.createTypedArrayList(GnssSignalType.CREATOR))
                                     .setPublishDateSeconds(in.readLong())
                                     .setStartDateSeconds(in.readLong())
                                     .setEndDateSeconds(in.readLong())
@@ -180,8 +198,8 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
-        parcel.writeInt(mSvid);
-        parcel.writeBoolean(mUsable);
+        parcel.writeInt(mBadSvid);
+        parcel.writeTypedList(mBadSignalTypes);
         parcel.writeLong(mPublishDateSeconds);
         parcel.writeLong(mStartDateSeconds);
         parcel.writeLong(mEndDateSeconds);
@@ -193,8 +211,8 @@
     @NonNull
     public String toString() {
         StringBuilder builder = new StringBuilder("RealTimeIntegrityModel[");
-        builder.append("svid = ").append(mSvid);
-        builder.append(", usable = ").append(mUsable);
+        builder.append("badSvid = ").append(mBadSvid);
+        builder.append(", badSignalTypes = ").append(mBadSignalTypes);
         builder.append(", publishDateSeconds = ").append(mPublishDateSeconds);
         builder.append(", startDateSeconds = ").append(mStartDateSeconds);
         builder.append(", endDateSeconds = ").append(mEndDateSeconds);
@@ -206,8 +224,8 @@
 
     /** Builder for {@link RealTimeIntegrityModel} */
     public static final class Builder {
-        private int mSvid;
-        private boolean mUsable;
+        private int mBadSvid;
+        private List<GnssSignalType> mBadSignalTypes;
         private long mPublishDateSeconds;
         private long mStartDateSeconds;
         private long mEndDateSeconds;
@@ -215,8 +233,7 @@
         private String mAdvisoryNumber;
 
         /**
-         * Sets the Pseudo-random or satellite ID number for the satellite,
-         * a.k.a. Space Vehicle (SV), or OSN number for Glonass.
+         * Sets the bad satellite ID number or OSN number for Glonass.
          *
          * <p>The distinction is made by looking at the constellation field. Values
          * must be in the range of:
@@ -228,15 +245,19 @@
          * <p> - Beidou: 1-63
          */
         @NonNull
-        public Builder setSvid(@IntRange(from = 1, to = 206) int svid) {
-            mSvid = svid;
+        public Builder setBadSvid(@IntRange(from = 1, to = 206) int badSvid) {
+            mBadSvid = badSvid;
             return this;
         }
 
-        /** Sets whether the satellite is usable or not. */
+        /**
+         * Sets the type of the bad signal or signals.
+         *
+         * <p>An empty list means that all signals on the specific SV are not healthy.
+         */
         @NonNull
-        public Builder setUsable(boolean usable) {
-            mUsable = usable;
+        public Builder setBadSignalTypes(@NonNull List<GnssSignalType> badSignalTypes) {
+            mBadSignalTypes = badSignalTypes;
             return this;
         }
 
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index 6d84e70..b91a5b5 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -673,6 +673,7 @@
         return config != null ? config.getDuration() : DURATION_NOT_SET;
     }
 
+    @Nullable
     private VolumeShaper.Configuration getVolumeShaperConfigFromWrapper(
             FadeVolumeShaperConfigsWrapper wrapper, boolean isFadeIn) {
         // if no volume shaper config is available, return null
diff --git a/nfc/tests/src/android/nfc/NfcManagerTest.java b/nfc/tests/src/android/nfc/NfcManagerTest.java
new file mode 100644
index 0000000..06314cc
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NfcManagerTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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 android.nfc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class NfcManagerTest {
+
+    private MockitoSession mMockitoSession;
+    private NfcManager mNfcManager;
+    @Mock
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mMockitoSession = ExtendedMockito.mockitoSession()
+                .mockStatic(NfcAdapter.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        MockitoAnnotations.initMocks(this);
+
+        when(NfcAdapter.getNfcAdapter(any())).thenReturn(mock(NfcAdapter.class));
+        when(mContext.getApplicationContext()).thenReturn(mContext);
+        mNfcManager = new NfcManager(mContext);
+    }
+
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
+    }
+
+    @Test
+    public void testGetDefaultAdapter() {
+        NfcAdapter nfcAdapter = mNfcManager.getDefaultAdapter();
+        assertThat(nfcAdapter).isNotNull();
+    }
+}
diff --git a/nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java b/nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java
new file mode 100644
index 0000000..6be95ad
--- /dev/null
+++ b/nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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 android.nfc.cardemulation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.nfc.INfcCardEmulation;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class CardemulationTest {
+
+    private CardEmulation mCardEmulation;
+    @Mock
+    private Context mContext;
+    @Mock
+    private INfcCardEmulation mINfcCardEmulation;
+    @Mock
+    private NfcAdapter mNfcAdapter;
+    @Mock
+    private PackageManager mPackageManager;
+    private MockitoSession mMockitoSession;
+
+    @Before
+    public void setUp() {
+        mMockitoSession = ExtendedMockito.mockitoSession()
+                .mockStatic(NfcAdapter.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        MockitoAnnotations.initMocks(this);
+
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION))
+                .thenReturn(true);
+        when(mContext.getApplicationContext()).thenReturn(mContext);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        assertThat(mNfcAdapter).isNotNull();
+        when(mNfcAdapter.getCardEmulationService()).thenReturn(mINfcCardEmulation);
+        when(mNfcAdapter.getContext()).thenReturn(mContext);
+        mCardEmulation = CardEmulation.getInstance(mNfcAdapter);
+    }
+
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
+    }
+
+    @Test
+    public void testIsDefaultServiceForCategory() throws RemoteException {
+        ComponentName componentName = mock(ComponentName.class);
+        UserHandle userHandle = mock(UserHandle.class);
+        when(userHandle.getIdentifier()).thenReturn(1);
+        when(mContext.getUser()).thenReturn(userHandle);
+        when(mINfcCardEmulation.isDefaultServiceForCategory(1, componentName,
+                "payment")).thenReturn(true);
+        boolean result = mCardEmulation.isDefaultServiceForCategory(componentName,
+                "payment");
+        assertThat(result).isTrue();
+        verify(mINfcCardEmulation).isDefaultServiceForCategory(1, componentName,
+                "payment");
+
+    }
+
+    @Test
+    public void testIsDefaultServiceForAid() throws RemoteException {
+        ComponentName componentName = mock(ComponentName.class);
+        UserHandle userHandle = mock(UserHandle.class);
+        when(userHandle.getIdentifier()).thenReturn(1);
+        when(mContext.getUser()).thenReturn(userHandle);
+        when(mINfcCardEmulation.isDefaultServiceForAid(1, componentName,
+                "payment")).thenReturn(true);
+        boolean result = mCardEmulation.isDefaultServiceForAid(componentName,
+                "payment");
+        assertThat(result).isTrue();
+        verify(mINfcCardEmulation).isDefaultServiceForAid(1, componentName,
+                "payment");
+    }
+}
diff --git a/nfc/tests/src/android/nfc/tech/NfcBTest.java b/nfc/tests/src/android/nfc/tech/NfcBTest.java
new file mode 100644
index 0000000..98d6070
--- /dev/null
+++ b/nfc/tests/src/android/nfc/tech/NfcBTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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 android.nfc.tech;
+
+import static android.nfc.tech.NfcB.EXTRA_APPDATA;
+import static android.nfc.tech.NfcB.EXTRA_PROTINFO;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.nfc.INfcTag;
+import android.nfc.Tag;
+import android.nfc.TransceiveResult;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+
+public class NfcBTest {
+    private final byte[] mSampleAppDate = new byte[] {1, 2, 3};
+    private final byte[] mSampleProtInfo = new byte[] {3, 2, 1};
+    @Mock
+    private Tag mMockTag;
+    @Mock
+    private Bundle mMockBundle;
+    @Mock
+    private INfcTag mMockTagService;
+    private NfcB mNfcB;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        when(mMockBundle.getByteArray(EXTRA_APPDATA)).thenReturn(mSampleAppDate);
+        when(mMockBundle.getByteArray(EXTRA_PROTINFO)).thenReturn(mSampleProtInfo);
+        when(mMockTag.getTechExtras(TagTechnology.NFC_B)).thenReturn(mMockBundle);
+
+        mNfcB = new NfcB(mMockTag);
+    }
+
+    @Test
+    public void testGetApplicationData() {
+        assertNotNull(mNfcB.getApplicationData());
+    }
+
+    @Test
+    public void testGetProtocolInfo() {
+        assertNotNull(mNfcB.getProtocolInfo());
+    }
+
+    @Test
+    public void testGetNfcBInstance() {
+        Tag tag = mock(Tag.class);
+        when(tag.hasTech(TagTechnology.NFC_B)).thenReturn(true);
+        when(tag.getTechExtras(TagTechnology.NFC_B)).thenReturn(mMockBundle);
+
+        assertNotNull(NfcB.get(tag));
+        verify(tag).hasTech(TagTechnology.NFC_B);
+        verify(tag).getTechExtras(TagTechnology.NFC_B);
+    }
+
+    @Test
+    public void testGetNfcBNullInstance() {
+        Tag tag = mock(Tag.class);
+        when(tag.hasTech(TagTechnology.NFC_B)).thenReturn(false);
+
+        assertNull(NfcB.get(tag));
+        verify(tag).hasTech(TagTechnology.NFC_B);
+        verify(tag, never()).getTechExtras(TagTechnology.NFC_B);
+    }
+
+
+    @Test
+    public void testTransceive() throws IOException, RemoteException {
+        byte[] sampleData = new byte[] {1, 2, 3, 4, 5};
+        TransceiveResult mockTransceiveResult = mock(TransceiveResult.class);
+        when(mMockTag.getConnectedTechnology()).thenReturn(TagTechnology.NFC_B);
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        when(mMockTag.getServiceHandle()).thenReturn(1);
+        when(mMockTagService.transceive(1, sampleData, true))
+                .thenReturn(mockTransceiveResult);
+        when(mockTransceiveResult.getResponseOrThrow()).thenReturn(sampleData);
+
+        mNfcB.transceive(sampleData);
+        verify(mMockTag).getTagService();
+        verify(mMockTag).getServiceHandle();
+    }
+
+    @Test
+    public void testGetMaxTransceiveLength() throws RemoteException {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        when(mMockTagService.getMaxTransceiveLength(TagTechnology.NFC_B)).thenReturn(1);
+
+        mNfcB.getMaxTransceiveLength();
+        verify(mMockTag).getTagService();
+    }
+}
diff --git a/nfc/tests/src/android/nfc/tech/NfcFTest.java b/nfc/tests/src/android/nfc/tech/NfcFTest.java
new file mode 100644
index 0000000..31a6943
--- /dev/null
+++ b/nfc/tests/src/android/nfc/tech/NfcFTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.tech;
+
+import static android.nfc.tech.NfcF.EXTRA_PMM;
+import static android.nfc.tech.NfcF.EXTRA_SC;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.nfc.ErrorCodes;
+import android.nfc.INfcTag;
+import android.nfc.Tag;
+import android.nfc.TransceiveResult;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+
+public class NfcFTest {
+    private final byte[] mSampleSystemCode = new byte[] {1, 2, 3};
+    private final byte[] mSampleManufacturer = new byte[] {3, 2, 1};
+    @Mock
+    private Tag mMockTag;
+    @Mock
+    private INfcTag mMockTagService;
+    @Mock
+    private Bundle mMockBundle;
+    private NfcF mNfcF;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        when(mMockBundle.getByteArray(EXTRA_SC)).thenReturn(mSampleSystemCode);
+        when(mMockBundle.getByteArray(EXTRA_PMM)).thenReturn(mSampleManufacturer);
+        when(mMockTag.getTechExtras(TagTechnology.NFC_F)).thenReturn(mMockBundle);
+
+        mNfcF = new NfcF(mMockTag);
+    }
+
+    @Test
+    public void testGetSystemCode() {
+        assertNotNull(mNfcF.getSystemCode());
+    }
+
+    @Test
+    public void testGetManufacturer() {
+        assertNotNull(mNfcF.getManufacturer());
+    }
+
+    @Test
+    public void testGetNfcFInstanceWithTech() {
+        Tag tag = mock(Tag.class);
+        when(tag.getTechExtras(TagTechnology.NFC_F)).thenReturn(mMockBundle);
+        when(tag.hasTech(TagTechnology.NFC_F)).thenReturn(true);
+
+        assertNotNull(NfcF.get(tag));
+        verify(tag).getTechExtras(TagTechnology.NFC_F);
+        verify(tag).hasTech(TagTechnology.NFC_F);
+    }
+
+    @Test
+    public void testGetNfcFInstanceWithoutTech() {
+        Tag tag = mock(Tag.class);
+        when(tag.hasTech(TagTechnology.NFC_F)).thenReturn(false);
+
+        assertNull(NfcF.get(tag));
+        verify(tag).hasTech(TagTechnology.NFC_F);
+        verify(tag, never()).getTechExtras(TagTechnology.NFC_F);
+    }
+
+    @Test
+    public void testTransceive() throws IOException, RemoteException {
+        byte[] sampleData = new byte[]{1, 2, 3, 4, 5};
+        TransceiveResult mockTransceiveResult = mock(TransceiveResult.class);
+        when(mMockTag.getConnectedTechnology()).thenReturn(TagTechnology.NFC_F);
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        when(mMockTag.getServiceHandle()).thenReturn(1);
+        when(mMockTagService.transceive(1, sampleData, true))
+                .thenReturn(mockTransceiveResult);
+        when(mockTransceiveResult.getResponseOrThrow()).thenReturn(sampleData);
+
+        mNfcF.transceive(sampleData);
+        verify(mMockTag).getTagService();
+        verify(mMockTag).getServiceHandle();
+    }
+
+    @Test
+    public void testGetMaxTransceiveLength() throws RemoteException {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        when(mMockTagService.getMaxTransceiveLength(TagTechnology.NFC_F)).thenReturn(1);
+
+        mNfcF.getMaxTransceiveLength();
+        verify(mMockTag).getTagService();
+    }
+
+    @Test
+    public void testGetTimeout() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.getTimeout(TagTechnology.NFC_F)).thenReturn(2000);
+
+            assertEquals(2000, mNfcF.getTimeout());
+            verify(mMockTag).getTagService();
+            verify(mMockTagService).getTimeout(TagTechnology.NFC_F);
+        } catch (Exception e) {
+            fail("Unexpected exception during valid getTimeout: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testGetTimeoutRemoteException() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.getTimeout(TagTechnology.NFC_F)).thenThrow(new RemoteException());
+
+            assertEquals(0, mNfcF.getTimeout());
+        } catch (Exception e) {
+            fail("Unexpected exception during RemoteException in getTimeout: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testSetTimeout() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.setTimeout(TagTechnology.NFC_F, 1000)).thenReturn(
+                    ErrorCodes.SUCCESS);
+
+            mNfcF.setTimeout(1000);
+            verify(mMockTag).getTagService();
+            verify(mMockTagService).setTimeout(TagTechnology.NFC_F, 1000);
+        } catch (Exception e) {
+            fail("Unexpected exception during valid setTimeout: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testSetTimeoutInvalidTimeout() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.setTimeout(TagTechnology.NFC_F, -1)).thenReturn(
+                    ErrorCodes.ERROR_TIMEOUT);
+
+            assertThrows(IllegalArgumentException.class, () -> mNfcF.setTimeout(-1));
+        } catch (Exception e) {
+            fail("Unexpected exception during invalid setTimeout: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testSetTimeoutRemoteException() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.setTimeout(TagTechnology.NFC_F, 1000)).thenThrow(
+                    new RemoteException());
+
+            mNfcF.setTimeout(1000);
+            verify(mMockTag).getTagService();
+            verify(mMockTagService).setTimeout(TagTechnology.NFC_F, 1000);
+        } catch (Exception e) {
+            fail("Unexpected exception during RemoteException in setTimeout: " + e.getMessage());
+        }
+    }
+}
diff --git a/packages/CrashRecovery/framework/api/system-current.txt b/packages/CrashRecovery/framework/api/system-current.txt
index 68429ea..ad17ec69 100644
--- a/packages/CrashRecovery/framework/api/system-current.txt
+++ b/packages/CrashRecovery/framework/api/system-current.txt
@@ -9,7 +9,7 @@
     method @NonNull public abstract java.util.List<java.lang.String> onGetRequestedPackages();
     method @NonNull public abstract java.util.List<android.service.watchdog.ExplicitHealthCheckService.PackageConfig> onGetSupportedPackages();
     method public abstract void onRequestHealthCheck(@NonNull String);
-    method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") public final void setHealthCheckResultCallback(@Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<android.os.Bundle>);
+    method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") public final void setHealthCheckPassedCallback(@Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<android.os.Bundle>);
     field public static final String BIND_PERMISSION = "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE";
     field @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") public static final String EXTRA_HEALTH_CHECK_PASSED_PACKAGE = "android.service.watchdog.extra.HEALTH_CHECK_PASSED_PACKAGE";
     field public static final String SERVICE_INTERFACE = "android.service.watchdog.ExplicitHealthCheckService";
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java b/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java
index b03e376..fdb0fc5 100644
--- a/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java
+++ b/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java
@@ -180,7 +180,7 @@
      *                 passed the health check.
      */
     @FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
-    public final void setHealthCheckResultCallback(@CallbackExecutor @Nullable Executor executor,
+    public final void setHealthCheckPassedCallback(@CallbackExecutor @Nullable Executor executor,
             @Nullable Consumer<Bundle> callback) {
         mCallbackExecutor = executor;
         mHealthCheckResultCallback = callback;
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
index 70f5bb3..ef46906 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
@@ -347,8 +347,8 @@
      *                 and boot loops.
      * @param executor Executor for the thread on which observers would receive callbacks
      */
-    public void registerHealthObserver(@NonNull PackageHealthObserver observer,
-            @NonNull @CallbackExecutor Executor executor) {
+    public void registerHealthObserver(@NonNull @CallbackExecutor Executor executor,
+            @NonNull PackageHealthObserver observer) {
         synchronized (sLock) {
             ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
             if (internalObserver != null) {
@@ -390,8 +390,8 @@
      *
      * @throws IllegalStateException if the observer was not previously registered
      */
-    public void startExplicitHealthCheck(@NonNull PackageHealthObserver observer,
-            @NonNull List<String> packageNames, long timeoutMs) {
+    public void startExplicitHealthCheck(@NonNull List<String> packageNames, long timeoutMs,
+            @NonNull PackageHealthObserver observer) {
         synchronized (sLock) {
             if (!mAllObservers.containsKey(observer.getUniqueIdentifier())) {
                 Slog.wtf(TAG, "No observer found, need to register the observer: "
@@ -767,14 +767,6 @@
     }
 
     /**
-     * Indicates that the result of a mitigation executed during
-     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
-     * {@link PackageHealthObserver#onExecuteBootLoopMitigation} is unknown.
-     */
-    public static final int MITIGATION_RESULT_UNKNOWN =
-            ObserverMitigationResult.MITIGATION_RESULT_UNKNOWN;
-
-    /**
      * Indicates that a mitigation was successfully triggered or executed during
      * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
      * {@link PackageHealthObserver#onExecuteBootLoopMitigation}.
@@ -790,23 +782,6 @@
     public static final int MITIGATION_RESULT_SKIPPED =
             ObserverMitigationResult.MITIGATION_RESULT_SKIPPED;
 
-    /**
-     * Indicates that a mitigation executed during
-     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
-     * {@link PackageHealthObserver#onExecuteBootLoopMitigation} failed,
-     * but the failure is potentially retryable.
-     */
-    public static final int MITIGATION_RESULT_FAILURE_RETRYABLE =
-            ObserverMitigationResult.MITIGATION_RESULT_FAILURE_RETRYABLE;
-
-    /**
-     * Indicates that a mitigation executed during
-     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
-     * {@link PackageHealthObserver#onExecuteBootLoopMitigation} failed,
-     * and the failure is not retryable.
-     */
-    public static final int MITIGATION_RESULT_FAILURE_NON_RETRYABLE =
-            ObserverMitigationResult.MITIGATION_RESULT_FAILURE_NON_RETRYABLE;
 
     /**
      * Possible return values of the for mitigations executed during
@@ -816,18 +791,12 @@
      */
     @Retention(SOURCE)
     @IntDef(prefix = "MITIGATION_RESULT_", value = {
-            ObserverMitigationResult.MITIGATION_RESULT_UNKNOWN,
             ObserverMitigationResult.MITIGATION_RESULT_SUCCESS,
             ObserverMitigationResult.MITIGATION_RESULT_SKIPPED,
-            ObserverMitigationResult.MITIGATION_RESULT_FAILURE_RETRYABLE,
-            ObserverMitigationResult.MITIGATION_RESULT_FAILURE_NON_RETRYABLE,
             })
     public @interface ObserverMitigationResult {
-        int MITIGATION_RESULT_UNKNOWN = 0;
         int MITIGATION_RESULT_SUCCESS = 1;
         int MITIGATION_RESULT_SKIPPED = 2;
-        int MITIGATION_RESULT_FAILURE_RETRYABLE = 3;
-        int MITIGATION_RESULT_FAILURE_NON_RETRYABLE = 4;
     }
 
     /**
@@ -921,11 +890,6 @@
          * @param mitigationCount the number of times mitigation has been called for this package
          *                         (including this time).
          * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful,
-         *         {@link #MITIGATION_RESULT_FAILURE_RETRYABLE} if the mitigation failed but can be
-         *         retried,
-         *         {@link #MITIGATION_RESULT_FAILURE_NON_RETRYABLE} if the mitigation failed and
-         *         cannot be retried,
-         *         {@link #MITIGATION_RESULT_UNKNOWN} if the result of the mitigation is unknown,
          *         or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped.
          */
         @ObserverMitigationResult int onExecuteHealthCheckMitigation(
@@ -957,11 +921,6 @@
          *                        boot loop (including this time).
          *
          * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful,
-         *         {@link #MITIGATION_RESULT_FAILURE_RETRYABLE} if the mitigation failed but can be
-         *         retried,
-         *         {@link #MITIGATION_RESULT_FAILURE_NON_RETRYABLE} if the mitigation failed and
-         *         cannot be retried,
-         *         {@link #MITIGATION_RESULT_UNKNOWN} if the result of the mitigation is unknown,
          *         or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped.
          */
         default @ObserverMitigationResult int onExecuteBootLoopMitigation(int mitigationCount) {
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
index bb9e962..40bc5f7 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
@@ -162,7 +162,7 @@
     /** Register the Rescue Party observer as a Package Watchdog health observer */
     public static void registerHealthObserver(Context context) {
         PackageWatchdog.getInstance(context).registerHealthObserver(
-                RescuePartyObserver.getInstance(context), context.getMainExecutor());
+                context.getMainExecutor(), RescuePartyObserver.getInstance(context));
     }
 
     private static boolean isDisabled() {
@@ -316,9 +316,9 @@
             Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
                     + updatedNamespace);
             PackageWatchdog.getInstance(context).startExplicitHealthCheck(
-                    rescuePartyObserver,
                     callingPackageList,
-                    DEFAULT_OBSERVING_DURATION_MS);
+                    DEFAULT_OBSERVING_DURATION_MS,
+                    rescuePartyObserver);
         }
     }
 
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index c75f3aa..4978df4 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -113,8 +113,8 @@
         dataDir.mkdirs();
         mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
         mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
-        PackageWatchdog.getInstance(mContext).registerHealthObserver(this,
-                context.getMainExecutor());
+        PackageWatchdog.getInstance(mContext).registerHealthObserver(context.getMainExecutor(),
+                this);
 
         if (SystemProperties.getBoolean("sys.boot_completed", false)) {
             // Load the value from the file if system server has crashed and restarted
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
index ac815f8..4a00ed3 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
@@ -363,7 +363,7 @@
      * it will resume observing any packages requested from a previous boot.
      * @hide
      */
-    public void registerHealthObserver(PackageHealthObserver observer, Executor ignoredExecutor) {
+    public void registerHealthObserver(Executor ignoredExecutor, PackageHealthObserver observer) {
         synchronized (mLock) {
             ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
             if (internalObserver != null) {
@@ -397,8 +397,8 @@
      * {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
      * @hide
      */
-    public void startExplicitHealthCheck(PackageHealthObserver observer, List<String> packageNames,
-            long durationMs) {
+    public void startExplicitHealthCheck(List<String> packageNames, long durationMs,
+            PackageHealthObserver observer) {
         if (packageNames.isEmpty()) {
             Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
             return;
@@ -446,7 +446,7 @@
             }
 
             // Register observer in case not already registered
-            registerHealthObserver(observer, null);
+            registerHealthObserver(null, observer);
 
             // Sync after we add the new packages to the observers. We may have received packges
             // requiring an earlier schedule than we are currently scheduled for.
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
index c6452d3..8251fb4 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
@@ -168,7 +168,7 @@
     /** Register the Rescue Party observer as a Package Watchdog health observer */
     public static void registerHealthObserver(Context context) {
         PackageWatchdog.getInstance(context).registerHealthObserver(
-                RescuePartyObserver.getInstance(context), null);
+                null, RescuePartyObserver.getInstance(context));
     }
 
     private static boolean isDisabled() {
@@ -390,9 +390,9 @@
             Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
                     + updatedNamespace);
             PackageWatchdog.getInstance(context).startExplicitHealthCheck(
-                    rescuePartyObserver,
                     callingPackageList,
-                    DEFAULT_OBSERVING_DURATION_MS);
+                    DEFAULT_OBSERVING_DURATION_MS,
+                    rescuePartyObserver);
         }
     }
 
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 0411537..5c4e57e 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -112,7 +112,7 @@
         dataDir.mkdirs();
         mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
         mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
-        PackageWatchdog.getInstance(mContext).registerHealthObserver(this, null);
+        PackageWatchdog.getInstance(mContext).registerHealthObserver(null, this);
         mApexManager = apexManager;
 
         if (SystemProperties.getBoolean("sys.boot_completed", false)) {
@@ -286,7 +286,7 @@
     @AnyThread
     @NonNull
     public void startObservingHealth(@NonNull List<String> packages, @NonNull long durationMs) {
-        PackageWatchdog.getInstance(mContext).startExplicitHealthCheck(this, packages, durationMs);
+        PackageWatchdog.getInstance(mContext).startExplicitHealthCheck(packages, durationMs, this);
     }
 
     @AnyThread
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index e029f3a..4da7359 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -24,6 +24,7 @@
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
     <uses-permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" />
+    <uses-permission android:name="android.permission.RESOLVE_COMPONENT_FOR_UID" />
 
     <uses-permission android:name="com.google.android.permission.INSTALL_WEARABLE_PACKAGES" />
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index 635ae20..6c06fab 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -26,6 +26,7 @@
 import android.content.ContentResolver;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
@@ -274,8 +275,20 @@
     }
 
     private boolean canPackageQuery(int callingUid, Uri packageUri) {
-        ProviderInfo info = mPackageManager.resolveContentProvider(packageUri.getAuthority(),
-                PackageManager.ComponentInfoFlags.of(0));
+        ProviderInfo info;
+        try {
+            if (Flags.uidBasedProviderLookup()) {
+                info = mPackageManager.resolveContentProviderForUid(packageUri.getAuthority(),
+                    PackageManager.ComponentInfoFlags.of(0), callingUid);
+            } else {
+                info = mPackageManager.resolveContentProvider(packageUri.getAuthority(),
+                    PackageManager.ComponentInfoFlags.of(0));
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Caller cannot access " + packageUri, e);
+            return false;
+        }
+
         if (info == null) {
             return false;
         }
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt
index 53507fe..8335ee4 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.datastore
 
+import android.Manifest
 import android.content.ContentResolver
 import android.content.Context
 import android.net.Uri
@@ -82,5 +83,11 @@
                             instance = it
                         }
                 }
+
+        /** Returns the required permissions to read [Global] settings. */
+        fun getReadPermissions() = arrayOf<String>()
+
+        /** Returns the required permissions to write [Global] settings. */
+        fun getWritePermissions() = arrayOf(Manifest.permission.WRITE_SECURE_SETTINGS)
     }
 }
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt
index ca7fd7b..c117b92 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.datastore
 
+import android.Manifest
 import android.content.ContentResolver
 import android.content.Context
 import android.net.Uri
@@ -82,5 +83,11 @@
                             instance = it
                         }
                 }
+
+        /** Returns the required permissions to read [Secure] settings. */
+        fun getReadPermissions() = arrayOf<String>()
+
+        /** Returns the required permissions to write [Secure] settings. */
+        fun getWritePermissions() = arrayOf(Manifest.permission.WRITE_SECURE_SETTINGS)
     }
 }
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt
index 20a74d3..f5a2f94 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.datastore
 
+import android.Manifest
 import android.content.ContentResolver
 import android.content.Context
 import android.net.Uri
@@ -82,5 +83,11 @@
                             instance = it
                         }
                 }
+
+        /** Returns the required permissions to read [System] settings. */
+        fun getReadPermissions() = arrayOf<String>()
+
+        /** Returns the required permissions to write [System] settings. */
+        fun getWritePermissions() = arrayOf(Manifest.permission.WRITE_SETTINGS)
     }
 }
diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
index f611793..2aa619a 100644
--- a/packages/SettingsLib/Graph/graph.proto
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -81,6 +81,10 @@
   optional PreferenceValueDescriptorProto value_descriptor = 15;
   // Indicate how sensitive of the preference.
   optional int32 sensitivity_level = 16;
+  // The required permissions to read preference value.
+  repeated string read_permissions = 17;
+  // The required permissions to write preference value.
+  repeated string write_permissions = 18;
 
   // Target of an Intent
   message ActionTarget {
@@ -108,6 +112,7 @@
   oneof value {
     bool boolean_value = 1;
     int32 int_value = 2;
+    float float_value = 3;
   }
 }
 
@@ -116,6 +121,7 @@
   oneof type {
     bool boolean_type = 1;
     RangeValueProto range_value = 2;
+    bool float_type = 3;
   }
 }
 
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index eaa7926..91dec03 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -42,6 +42,7 @@
 import com.android.settingslib.graph.proto.PreferenceScreenProto
 import com.android.settingslib.graph.proto.TextProto
 import com.android.settingslib.metadata.BooleanValue
+import com.android.settingslib.metadata.FloatPersistentPreference
 import com.android.settingslib.metadata.PersistentPreference
 import com.android.settingslib.metadata.PreferenceAvailabilityProvider
 import com.android.settingslib.metadata.PreferenceHierarchy
@@ -390,7 +391,13 @@
     }
     persistent = metadata.isPersistent(context)
     if (persistent) {
-        if (metadata is PersistentPreference<*>) sensitivityLevel = metadata.sensitivityLevel
+        if (metadata is PersistentPreference<*>) {
+            sensitivityLevel = metadata.sensitivityLevel
+            val readPermissions = metadata.getReadPermissions(context)
+            readPermissions.forEach { addReadPermissions(it) }
+            val writePermissions = metadata.getWritePermissions(context)
+            writePermissions.forEach { addWritePermissions(it) }
+        }
         if (
             flags.includeValue() &&
                 enabled &&
@@ -399,15 +406,13 @@
                 metadata is PersistentPreference<*> &&
                 metadata.evalReadPermit(context, callingPid, callingUid) == ReadWritePermit.ALLOW
         ) {
+            val storage = metadata.storage(context)
             value = preferenceValueProto {
                 when (metadata) {
-                    is BooleanValue ->
-                        metadata.storage(context).getBoolean(metadata.key)?.let {
-                            booleanValue = it
-                        }
-                    is RangeValue -> {
-                        metadata.storage(context).getInt(metadata.key)?.let { intValue = it }
-                    }
+                    is BooleanValue -> storage.getBoolean(metadata.key)?.let { booleanValue = it }
+                    is RangeValue -> storage.getInt(metadata.key)?.let { intValue = it }
+                    is FloatPersistentPreference ->
+                        storage.getFloat(metadata.key)?.let { floatValue = it }
                     else -> {}
                 }
             }
@@ -421,6 +426,7 @@
                             max = metadata.getMaxValue(context)
                             step = metadata.getIncrementStep(context)
                         }
+                    is FloatPersistentPreference -> floatType = true
                     else -> {}
                 }
             }
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index d72ba08..83c4304 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -159,6 +159,12 @@
                 }
                 storage.setInt(key, intValue)
                 return PreferenceSetterResult.OK
+            } else if (value.hasFloatValue()) {
+                val floatValue = value.floatValue
+                val resultCode = metadata.checkWritePermit(floatValue)
+                if (resultCode != PreferenceSetterResult.OK) return resultCode
+                storage.setFloat(key, floatValue)
+                return PreferenceSetterResult.OK
             }
         } catch (e: Exception) {
             return PreferenceSetterResult.INTERNAL_ERROR
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
index dee32d9..adbe773 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
@@ -35,8 +35,9 @@
 
 /** Kotlin DSL-style builder for [PreferenceScreenProto]. */
 @JvmSynthetic
-inline fun preferenceScreenProto(init: PreferenceScreenProto.Builder.() -> Unit) =
-    PreferenceScreenProto.newBuilder().also(init).build()
+inline fun preferenceScreenProto(
+    init: PreferenceScreenProto.Builder.() -> Unit
+): PreferenceScreenProto = PreferenceScreenProto.newBuilder().also(init).build()
 
 /** Returns preference or null. */
 val PreferenceOrGroupProto.preferenceOrNull
@@ -48,8 +49,9 @@
 
 /** Kotlin DSL-style builder for [PreferenceOrGroupProto]. */
 @JvmSynthetic
-inline fun preferenceOrGroupProto(init: PreferenceOrGroupProto.Builder.() -> Unit) =
-    PreferenceOrGroupProto.newBuilder().also(init).build()
+inline fun preferenceOrGroupProto(
+    init: PreferenceOrGroupProto.Builder.() -> Unit
+): PreferenceOrGroupProto = PreferenceOrGroupProto.newBuilder().also(init).build()
 
 /** Returns preference or null. */
 val PreferenceGroupProto.preferenceOrNull
@@ -57,8 +59,9 @@
 
 /** Kotlin DSL-style builder for [PreferenceGroupProto]. */
 @JvmSynthetic
-inline fun preferenceGroupProto(init: PreferenceGroupProto.Builder.() -> Unit) =
-    PreferenceGroupProto.newBuilder().also(init).build()
+inline fun preferenceGroupProto(
+    init: PreferenceGroupProto.Builder.() -> Unit
+): PreferenceGroupProto = PreferenceGroupProto.newBuilder().also(init).build()
 
 /** Returns title or null. */
 val PreferenceProto.titleOrNull
@@ -74,7 +77,7 @@
 
 /** Kotlin DSL-style builder for [PreferenceProto]. */
 @JvmSynthetic
-inline fun preferenceProto(init: PreferenceProto.Builder.() -> Unit) =
+inline fun preferenceProto(init: PreferenceProto.Builder.() -> Unit): PreferenceProto =
     PreferenceProto.newBuilder().also(init).build()
 
 /** Returns intent or null. */
@@ -83,39 +86,42 @@
 
 /** Kotlin DSL-style builder for [ActionTarget]. */
 @JvmSynthetic
-inline fun actionTargetProto(init: ActionTarget.Builder.() -> Unit) =
+inline fun actionTargetProto(init: ActionTarget.Builder.() -> Unit): ActionTarget =
     ActionTarget.newBuilder().also(init).build()
 
 /** Kotlin DSL-style builder for [PreferenceValueProto]. */
 @JvmSynthetic
-inline fun preferenceValueProto(init: PreferenceValueProto.Builder.() -> Unit) =
-    PreferenceValueProto.newBuilder().also(init).build()
+inline fun preferenceValueProto(
+    init: PreferenceValueProto.Builder.() -> Unit
+): PreferenceValueProto = PreferenceValueProto.newBuilder().also(init).build()
 
 /** Kotlin DSL-style builder for [PreferenceValueDescriptorProto]. */
 @JvmSynthetic
-inline fun preferenceValueDescriptorProto(init: PreferenceValueDescriptorProto.Builder.() -> Unit) =
-    PreferenceValueDescriptorProto.newBuilder().also(init).build()
+inline fun preferenceValueDescriptorProto(
+    init: PreferenceValueDescriptorProto.Builder.() -> Unit
+): PreferenceValueDescriptorProto = PreferenceValueDescriptorProto.newBuilder().also(init).build()
 
 /** Kotlin DSL-style builder for [RangeValueProto]. */
 @JvmSynthetic
-inline fun rangeValueProto(init: RangeValueProto.Builder.() -> Unit) =
+inline fun rangeValueProto(init: RangeValueProto.Builder.() -> Unit): RangeValueProto =
     RangeValueProto.newBuilder().also(init).build()
 
 /** Kotlin DSL-style builder for [TextProto]. */
 @JvmSynthetic
-inline fun textProto(init: TextProto.Builder.() -> Unit) = TextProto.newBuilder().also(init).build()
+inline fun textProto(init: TextProto.Builder.() -> Unit): TextProto =
+    TextProto.newBuilder().also(init).build()
 
 /** Kotlin DSL-style builder for [IntentProto]. */
 @JvmSynthetic
-inline fun intentProto(init: IntentProto.Builder.() -> Unit) =
+inline fun intentProto(init: IntentProto.Builder.() -> Unit): IntentProto =
     IntentProto.newBuilder().also(init).build()
 
 /** Kotlin DSL-style builder for [BundleProto]. */
 @JvmSynthetic
-inline fun bundleProto(init: BundleProto.Builder.() -> Unit) =
+inline fun bundleProto(init: BundleProto.Builder.() -> Unit): BundleProto =
     BundleProto.newBuilder().also(init).build()
 
 /** Kotlin DSL-style builder for [BundleValue]. */
 @JvmSynthetic
-inline fun bundleValueProto(init: BundleValue.Builder.() -> Unit) =
+inline fun bundleValueProto(init: BundleValue.Builder.() -> Unit): BundleValue =
     BundleValue.newBuilder().also(init).build()
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index adc4f316..bc4f1f9 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -167,6 +167,7 @@
         if (mLottieDynamicColor) {
             LottieColorUtils.applyDynamicColors(getContext(), illustrationView);
         }
+        LottieColorUtils.applyMaterialColor(getContext(), illustrationView);
 
         if (mOnBindListener != null) {
             mOnBindListener.onBind(illustrationView);
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
index 98a7290..4421424 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
@@ -21,14 +21,14 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 
-import com.android.settingslib.color.R;
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.widget.theme.R;
 
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieProperty;
 import com.airbnb.lottie.model.KeyPath;
 
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -37,52 +37,97 @@
  */
 public class LottieColorUtils {
     private static final Map<String, Integer> DARK_TO_LIGHT_THEME_COLOR_MAP;
+    private static final Map<String, Integer> MATERIAL_COLOR_MAP;
 
     static {
-        HashMap<String, Integer> map = new HashMap<>();
-        map.put(
-                ".grey200",
-                R.color.settingslib_color_grey800);
-        map.put(
-                ".grey600",
-                R.color.settingslib_color_grey400);
-        map.put(
-                ".grey800",
-                R.color.settingslib_color_grey300);
-        map.put(
-                ".grey900",
-                R.color.settingslib_color_grey50);
-        map.put(
-                ".red100",
-                R.color.settingslib_color_red500);
-        map.put(
-                ".red200",
-                R.color.settingslib_color_red500);
-        map.put(
-                ".red400",
-                R.color.settingslib_color_red600);
-        map.put(
-                ".black",
-                android.R.color.white);
-        map.put(
-                ".blue200",
-                R.color.settingslib_color_blue700);
-        map.put(
-                ".blue400",
-                R.color.settingslib_color_blue600);
-        map.put(
-                ".green100",
-                R.color.settingslib_color_green500);
-        map.put(
-                ".green200",
-                R.color.settingslib_color_green500);
-        map.put(
-                ".green400",
-                R.color.settingslib_color_green600);
-        map.put(
-                ".cream",
-                R.color.settingslib_color_charcoal);
-        DARK_TO_LIGHT_THEME_COLOR_MAP = Collections.unmodifiableMap(map);
+        DARK_TO_LIGHT_THEME_COLOR_MAP = Map.ofEntries(
+                Map.entry(".grey200",
+                        com.android.settingslib.color.R.color.settingslib_color_grey800),
+                Map.entry(".grey600",
+                        com.android.settingslib.color.R.color.settingslib_color_grey400),
+                Map.entry(".grey800",
+                        com.android.settingslib.color.R.color.settingslib_color_grey300),
+                Map.entry(".grey900",
+                        com.android.settingslib.color.R.color.settingslib_color_grey50),
+                Map.entry(".red100",
+                        com.android.settingslib.color.R.color.settingslib_color_red500),
+                Map.entry(".red200",
+                        com.android.settingslib.color.R.color.settingslib_color_red500),
+                Map.entry(".red400",
+                        com.android.settingslib.color.R.color.settingslib_color_red600),
+                Map.entry(".black",
+                        android.R.color.white),
+                Map.entry(".blue200",
+                        com.android.settingslib.color.R.color.settingslib_color_blue700),
+                Map.entry(".blue400",
+                        com.android.settingslib.color.R.color.settingslib_color_blue600),
+                Map.entry(".green100",
+                        com.android.settingslib.color.R.color.settingslib_color_green500),
+                Map.entry(".green200",
+                        com.android.settingslib.color.R.color.settingslib_color_green500),
+                Map.entry(".green400",
+                        com.android.settingslib.color.R.color.settingslib_color_green600),
+                Map.entry(".cream",
+                        com.android.settingslib.color.R.color.settingslib_color_charcoal));
+
+        MATERIAL_COLOR_MAP = Map.ofEntries(
+                Map.entry(".primary", R.color.settingslib_materialColorPrimary),
+                Map.entry(".onPrimary", R.color.settingslib_materialColorOnPrimary),
+                Map.entry(".primaryContainer", R.color.settingslib_materialColorPrimaryContainer),
+                Map.entry(".onPrimaryContainer",
+                        R.color.settingslib_materialColorOnPrimaryContainer),
+                Map.entry(".primaryInverse", R.color.settingslib_materialColorPrimaryInverse),
+                Map.entry(".primaryFixed", R.color.settingslib_materialColorPrimaryFixed),
+                Map.entry(".primaryFixedDim", R.color.settingslib_materialColorPrimaryFixedDim),
+                Map.entry(".onPrimaryFixed", R.color.settingslib_materialColorOnPrimaryFixed),
+                Map.entry(".onPrimaryFixedVariant",
+                        R.color.settingslib_materialColorOnPrimaryFixedVariant),
+                Map.entry(".secondary", R.color.settingslib_materialColorSecondary),
+                Map.entry(".onSecondary", R.color.settingslib_materialColorOnSecondary),
+                Map.entry(".secondaryContainer",
+                        R.color.settingslib_materialColorSecondaryContainer),
+                Map.entry(".onSecondaryContainer",
+                        R.color.settingslib_materialColorOnSecondaryContainer),
+                Map.entry(".secondaryFixed", R.color.settingslib_materialColorSecondaryFixed),
+                Map.entry(".secondaryFixedDim", R.color.settingslib_materialColorSecondaryFixedDim),
+                Map.entry(".onSecondaryFixed", R.color.settingslib_materialColorOnSecondaryFixed),
+                Map.entry(".onSecondaryFixedVariant",
+                        R.color.settingslib_materialColorOnSecondaryFixedVariant),
+                Map.entry(".tertiary", R.color.settingslib_materialColorTertiary),
+                Map.entry(".onTertiary", R.color.settingslib_materialColorOnTertiary),
+                Map.entry(".tertiaryContainer", R.color.settingslib_materialColorTertiaryContainer),
+                Map.entry(".onTertiaryContainer",
+                        R.color.settingslib_materialColorOnTertiaryContainer),
+                Map.entry(".tertiaryFixed", R.color.settingslib_materialColorTertiaryFixed),
+                Map.entry(".tertiaryFixedDim", R.color.settingslib_materialColorTertiaryFixedDim),
+                Map.entry(".onTertiaryFixed", R.color.settingslib_materialColorOnTertiaryFixed),
+                Map.entry(".onTertiaryFixedVariant",
+                        R.color.settingslib_materialColorOnTertiaryFixedVariant),
+                Map.entry(".error", R.color.settingslib_materialColorError),
+                Map.entry(".onError", R.color.settingslib_materialColorOnError),
+                Map.entry(".errorContainer", R.color.settingslib_materialColorErrorContainer),
+                Map.entry(".onErrorContainer", R.color.settingslib_materialColorOnErrorContainer),
+                Map.entry(".outline", R.color.settingslib_materialColorOutline),
+                Map.entry(".outlineVariant", R.color.settingslib_materialColorOutlineVariant),
+                Map.entry(".background", R.color.settingslib_materialColorBackground),
+                Map.entry(".onBackground", R.color.settingslib_materialColorOnBackground),
+                Map.entry(".surface", R.color.settingslib_materialColorSurface),
+                Map.entry(".onSurface", R.color.settingslib_materialColorOnSurface),
+                Map.entry(".surfaceVariant", R.color.settingslib_materialColorSurfaceVariant),
+                Map.entry(".onSurfaceVariant", R.color.settingslib_materialColorOnSurfaceVariant),
+                Map.entry(".surfaceInverse", R.color.settingslib_materialColorSurfaceInverse),
+                Map.entry(".onSurfaceInverse", R.color.settingslib_materialColorOnSurfaceInverse),
+                Map.entry(".surfaceBright", R.color.settingslib_materialColorSurfaceBright),
+                Map.entry(".surfaceDim", R.color.settingslib_materialColorSurfaceDim),
+                Map.entry(".surfaceContainer", R.color.settingslib_materialColorSurfaceContainer),
+                Map.entry(".surfaceContainerLow",
+                        R.color.settingslib_materialColorSurfaceContainerLow),
+                Map.entry(".surfaceContainerLowest",
+                        R.color.settingslib_materialColorSurfaceContainerLowest),
+                Map.entry(".surfaceContainerHigh",
+                        R.color.settingslib_materialColorSurfaceContainerHigh),
+                Map.entry(".surfaceContainerHighest",
+                        R.color.settingslib_materialColorSurfaceContainerHighest));
     }
 
     private LottieColorUtils() {
@@ -108,4 +153,20 @@
                     frameInfo -> new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
         }
     }
+
+    /** Applies material colors. */
+    public static void applyMaterialColor(@NonNull Context context,
+            @NonNull LottieAnimationView lottieAnimationView) {
+        if (!SettingsThemeHelper.isExpressiveTheme(context)) {
+            return;
+        }
+
+        for (String key : MATERIAL_COLOR_MAP.keySet()) {
+            final int color = context.getColor(MATERIAL_COLOR_MAP.get(key));
+            lottieAnimationView.addValueCallback(
+                    new KeyPath("**", key, "**"),
+                    LottieProperty.COLOR_FILTER,
+                    frameInfo -> new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
+        }
+    }
 }
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
index d3a7316..3dd6c47 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
@@ -201,3 +201,6 @@
     override fun isValidValue(context: Context, index: Int) =
         index in getMinValue(context)..getMaxValue(context)
 }
+
+/** A persistent preference that has a float value. */
+interface FloatPersistentPreference : PersistentPreference<Float>
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index d82b58e..cc996c5 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -199,3 +199,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "disable_audio_sharing_auto_pick_fallback_in_ui"
+    namespace: "cross_device_experiences"
+    description: "Do not auto pick audio sharing fallback device in UI"
+    bug: "383469911"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index e1929b7..6cf9e83 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -229,6 +229,8 @@
     <string name="bluetooth_hearing_aid_right_active">Active (right only)</string>
     <!-- Connected device settings. Message when the left-side and right-side hearing aids device are active. [CHAR LIMIT=NONE] -->
     <string name="bluetooth_hearing_aid_left_and_right_active">Active (left and right)</string>
+    <!-- Connected device settings.: Message when changing remote ambient state failed. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_hearing_device_ambient_error">Couldn\u2019t update surroundings</string>
 
     <!-- Connected devices settings. Message when Bluetooth is connected and active for media only, showing remote device status and battery level. [CHAR LIMIT=NONE] -->
     <string name="bluetooth_active_media_only_battery_level">Active (media only). <xliff:g id="battery_level_as_percentage">%1$s</xliff:g> battery.</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUi.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUi.java
new file mode 100644
index 0000000..881a97b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUi.java
@@ -0,0 +1,170 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+/** Interface for the ambient volume UI. */
+public interface AmbientVolumeUi {
+
+    /** Interface definition for a callback to be invoked when event happens in AmbientVolumeUi. */
+    interface AmbientVolumeUiListener {
+        /** Called when the expand icon is clicked. */
+        void onExpandIconClick();
+
+        /** Called when the ambient volume icon is clicked. */
+        void onAmbientVolumeIconClick();
+
+        /** Called when the slider of the specified side is changed. */
+        void onSliderValueChange(int side, int value);
+    };
+
+    /** The rotation degree of the expand icon when the UI is in collapsed mode. */
+    float ROTATION_COLLAPSED = 0f;
+    /** The rotation degree of the expand icon when the UI is in expanded mode. */
+    float ROTATION_EXPANDED = 180f;
+
+    /**
+     * The default ambient volume level for hearing device ambient volume icon
+     *
+     * <p> This icon visually represents the current ambient volume. It displays separate
+     * levels for the left and right sides, each with 5 levels ranging from 0 to 4.
+     *
+     * <p> To represent the combined left/right levels with a single value, the following
+     * calculation is used:
+     *      finalLevel = (leftLevel * 5) + rightLevel
+     * For example:
+     * <ul>
+     *    <li>If left level is 2 and right level is 3, the final level will be 13 (2 * 5 + 3)</li>
+     *    <li>If both left and right levels are 0, the final level will be 0</li>
+     *    <li>If both left and right levels are 4, the final level will be 24</li>
+     * </ul>
+     */
+    int AMBIENT_VOLUME_LEVEL_DEFAULT = 24;
+    /**
+     * The minimum ambient volume level for hearing device ambient volume icon
+     *
+     * @see #AMBIENT_VOLUME_LEVEL_DEFAULT
+     */
+    int AMBIENT_VOLUME_LEVEL_MIN = 0;
+    /**
+     * The maximum ambient volume level for hearing device ambient volume icon
+     *
+     * @see #AMBIENT_VOLUME_LEVEL_DEFAULT
+     */
+    int AMBIENT_VOLUME_LEVEL_MAX = 24;
+
+    /**
+     * Ths side identifier for slider in collapsed mode which can unified control the ambient
+     * volume of all devices in the same set.
+     */
+    int SIDE_UNIFIED = 999;
+
+    /** All valid side of the sliders in the UI. */
+    List<Integer> VALID_SIDES = List.of(SIDE_UNIFIED, SIDE_LEFT, SIDE_RIGHT);
+
+    /** Sets if the UI is visible. */
+    void setVisible(boolean visible);
+
+    /**
+     * Sets if the UI is expandable between expanded and collapsed mode.
+     *
+     * <p> If the UI is not expandable, it implies the UI will always stay in collapsed mode
+     */
+    void setExpandable(boolean expandable);
+
+    /** @return if the UI is expandable. */
+    boolean isExpandable();
+
+    /** Sets if the UI is in expanded mode. */
+    void setExpanded(boolean expanded);
+
+    /** @return if the UI is in expanded mode. */
+    boolean isExpanded();
+
+    /**
+     * Sets if the UI is capable to mute the ambient of the remote device.
+     *
+     * <p> If the value is {@code false}, it implies the remote device ambient will always be
+     * unmute and can not be mute from the UI
+     */
+    void setMutable(boolean mutable);
+
+    /** @return if the UI is capable to mute the ambient of remote device. */
+    boolean isMutable();
+
+    /** Sets if the UI shows mute state. */
+    void setMuted(boolean muted);
+
+    /** @return if the UI shows mute state */
+    boolean isMuted();
+
+    /**
+     * Sets listener on the UI.
+     *
+     * @see AmbientVolumeUiListener
+     */
+    void setListener(@Nullable AmbientVolumeUiListener listener);
+
+    /**
+     * Sets up sliders in the UI.
+     *
+     * <p> For each side of device, the UI should hava a corresponding slider to control it's
+     * ambient volume.
+     * <p> For all devices in the same set, the UI should have a slider to control all devices'
+     * ambient volume at once.
+     * @param sideToDeviceMap the side and device mapping of all devices in the same set
+     */
+    void setupSliders(@NonNull Map<Integer, BluetoothDevice> sideToDeviceMap);
+
+    /**
+     * Sets if the slider is enabled.
+     *
+     * @param side the side of the slider
+     * @param enabled the enabled state
+     */
+    void setSliderEnabled(int side, boolean enabled);
+
+    /**
+     * Sets the slider value.
+     *
+     * @param side the side of the slider
+     * @param value the ambient value
+     */
+    void setSliderValue(int side, int value);
+
+    /**
+     * Sets the slider's minimum and maximum value.
+     *
+     * @param side the side of the slider
+     * @param min the minimum ambient value
+     * @param max the maximum ambient value
+     */
+    void setSliderRange(int side, int min, int max);
+
+    /** Updates the UI according to current state. */
+    void updateLayout();
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUiController.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUiController.java
new file mode 100644
index 0000000..ce392b12
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUiController.java
@@ -0,0 +1,527 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
+import static android.bluetooth.AudioInputControl.MUTE_MUTED;
+import static android.bluetooth.BluetoothDevice.BOND_BONDED;
+
+import static com.android.settingslib.bluetooth.AmbientVolumeUi.SIDE_UNIFIED;
+import static com.android.settingslib.bluetooth.AmbientVolumeUi.VALID_SIDES;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_INVALID;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+import static com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data.INVALID_VOLUME;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.ArraySet;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.R;
+import com.android.settingslib.utils.ThreadUtils;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+
+import java.util.Map;
+import java.util.Set;
+
+/** This class controls ambient volume UI with local and remote ambient data. */
+public class AmbientVolumeUiController implements
+        HearingDeviceLocalDataManager.OnDeviceLocalDataChangeListener,
+        AmbientVolumeController.AmbientVolumeControlCallback,
+        AmbientVolumeUi.AmbientVolumeUiListener, BluetoothCallback, CachedBluetoothDevice.Callback {
+
+    private static final boolean DEBUG = true;
+    private static final String TAG = "AmbientVolumeUiController";
+
+    private final Context mContext;
+    private final LocalBluetoothProfileManager mProfileManager;
+    private final BluetoothEventManager mEventManager;
+    private final AmbientVolumeUi mAmbientLayout;
+    private final AmbientVolumeController mVolumeController;
+    private final HearingDeviceLocalDataManager mLocalDataManager;
+
+    private final Set<CachedBluetoothDevice> mCachedDevices = new ArraySet<>();
+    private final BiMap<Integer, BluetoothDevice> mSideToDeviceMap = HashBiMap.create();
+    private CachedBluetoothDevice mCachedDevice;
+    private boolean mShowUiWhenLocalDataExist = true;
+
+    public AmbientVolumeUiController(@NonNull Context context,
+            @NonNull LocalBluetoothManager bluetoothManager,
+            @NonNull AmbientVolumeUi ambientLayout) {
+        mContext = context;
+        mProfileManager = bluetoothManager.getProfileManager();
+        mEventManager = bluetoothManager.getEventManager();
+        mAmbientLayout = ambientLayout;
+        mAmbientLayout.setListener(this);
+        mVolumeController = new AmbientVolumeController(mProfileManager, this);
+        mLocalDataManager = new HearingDeviceLocalDataManager(context);
+        mLocalDataManager.setOnDeviceLocalDataChangeListener(this,
+                ThreadUtils.getBackgroundExecutor());
+    }
+
+    @VisibleForTesting
+    public AmbientVolumeUiController(@NonNull Context context,
+            @NonNull LocalBluetoothManager bluetoothManager,
+            @NonNull AmbientVolumeUi ambientLayout,
+            @NonNull AmbientVolumeController volumeController,
+            @NonNull HearingDeviceLocalDataManager localDataManager) {
+        mContext = context;
+        mProfileManager = bluetoothManager.getProfileManager();
+        mEventManager = bluetoothManager.getEventManager();
+        mAmbientLayout = ambientLayout;
+        mVolumeController = volumeController;
+        mLocalDataManager = localDataManager;
+    }
+
+
+    @Override
+    public void onDeviceLocalDataChange(@NonNull String address,
+            @Nullable HearingDeviceLocalDataManager.Data data) {
+        if (data == null) {
+            // The local data is removed because the device is unpaired, do nothing
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "onDeviceLocalDataChange, address:" + address + ", data:" + data);
+        }
+        for (BluetoothDevice device : mSideToDeviceMap.values()) {
+            if (device.getAnonymizedAddress().equals(address)) {
+                postOnMainThread(() -> loadLocalDataToUi(device));
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void onVolumeControlServiceConnected() {
+        mCachedDevices.forEach(device -> mVolumeController.registerCallback(
+                ThreadUtils.getBackgroundExecutor(), device.getDevice()));
+    }
+
+    @Override
+    public void onAmbientChanged(@NonNull BluetoothDevice device, int gainSettings) {
+        if (DEBUG) {
+            Log.d(TAG, "onAmbientChanged, value:" + gainSettings + ", device:" + device);
+        }
+        HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(device);
+        final boolean expanded = mAmbientLayout.isExpanded();
+        final boolean isInitiatedFromUi = (expanded && data.ambient() == gainSettings)
+                || (!expanded && data.groupAmbient() == gainSettings);
+        if (isInitiatedFromUi) {
+            // The change is initiated from UI, no need to update UI
+            return;
+        }
+
+        // We have to check if we need to expand the controls by getting all remote
+        // device's ambient value, delay for a while to wait all remote devices update
+        // to the latest value to avoid unnecessary expand action.
+        postDelayedOnMainThread(this::refresh, 1200L);
+    }
+
+    @Override
+    public void onMuteChanged(@NonNull BluetoothDevice device, int mute) {
+        if (DEBUG) {
+            Log.d(TAG, "onMuteChanged, mute:" + mute + ", device:" + device);
+        }
+        final boolean muted = mAmbientLayout.isMuted();
+        boolean isInitiatedFromUi = (muted && mute == MUTE_MUTED)
+                || (!muted && mute == MUTE_NOT_MUTED);
+        if (isInitiatedFromUi) {
+            // The change is initiated from UI, no need to update UI
+            return;
+        }
+
+        // We have to check if we need to mute the devices by getting all remote
+        // device's mute state, delay for a while to wait all remote devices update
+        // to the latest value.
+        postDelayedOnMainThread(this::refresh, 1200L);
+    }
+
+    @Override
+    public void onCommandFailed(@NonNull BluetoothDevice device) {
+        Log.w(TAG, "onCommandFailed, device:" + device);
+        postOnMainThread(() -> {
+            showErrorToast(R.string.bluetooth_hearing_device_ambient_error);
+            refresh();
+        });
+    }
+
+    @Override
+    public void onExpandIconClick() {
+        mSideToDeviceMap.forEach((s, d) -> {
+            if (!mAmbientLayout.isMuted()) {
+                // Apply previous collapsed/expanded volume to remote device
+                HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(d);
+                int volume = mAmbientLayout.isExpanded()
+                        ? data.ambient() : data.groupAmbient();
+                mVolumeController.setAmbient(d, volume);
+            }
+            // Update new value to local data
+            mLocalDataManager.updateAmbientControlExpanded(d,
+                    mAmbientLayout.isExpanded());
+        });
+        mLocalDataManager.flush();
+    }
+
+    @Override
+    public void onAmbientVolumeIconClick() {
+        if (!mAmbientLayout.isMuted()) {
+            loadLocalDataToUi();
+        }
+        for (BluetoothDevice device : mSideToDeviceMap.values()) {
+            mVolumeController.setMuted(device, mAmbientLayout.isMuted());
+        }
+    }
+
+    @Override
+    public void onSliderValueChange(int side, int value) {
+        if (DEBUG) {
+            Log.d(TAG, "onSliderValueChange: side=" + side + ", value=" + value);
+        }
+        setVolumeIfValid(side, value);
+
+        Runnable setAmbientRunnable = () -> {
+            if (side == SIDE_UNIFIED) {
+                mSideToDeviceMap.forEach((s, d) -> mVolumeController.setAmbient(d, value));
+            } else {
+                final BluetoothDevice device = mSideToDeviceMap.get(side);
+                mVolumeController.setAmbient(device, value);
+            }
+        };
+
+        if (mAmbientLayout.isMuted()) {
+            // User drag on the volume slider when muted. Unmute the devices first.
+            mAmbientLayout.setMuted(false);
+
+            for (BluetoothDevice device : mSideToDeviceMap.values()) {
+                mVolumeController.setMuted(device, false);
+            }
+            // Restore the value before muted
+            loadLocalDataToUi();
+            // Delay set ambient on remote device since the immediately sequential command
+            // might get failed sometimes
+            postDelayedOnMainThread(setAmbientRunnable, 1000L);
+        } else {
+            setAmbientRunnable.run();
+        }
+    }
+
+    @Override
+    public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
+            int state, int bluetoothProfile) {
+        if (bluetoothProfile == BluetoothProfile.VOLUME_CONTROL
+                && state == BluetoothProfile.STATE_CONNECTED
+                && mCachedDevices.contains(cachedDevice)) {
+            // After VCP connected, AICS may not ready yet and still return invalid value, delay
+            // a while to wait AICS ready as a workaround
+            postDelayedOnMainThread(this::refresh, 1000L);
+        }
+    }
+
+    @Override
+    public void onDeviceAttributesChanged() {
+        mCachedDevices.forEach(device -> {
+            device.unregisterCallback(this);
+            mVolumeController.unregisterCallback(device.getDevice());
+        });
+        postOnMainThread(()-> {
+            loadDevice(mCachedDevice);
+            ThreadUtils.postOnBackgroundThread(()-> {
+                mCachedDevices.forEach(device -> {
+                    device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
+                    mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
+                            device.getDevice());
+                });
+            });
+        });
+    }
+
+    /**
+     * Registers callbacks and listeners, this should be called when needs to start listening to
+     * events.
+     */
+    public void start() {
+        mEventManager.registerCallback(this);
+        mLocalDataManager.start();
+        mCachedDevices.forEach(device -> {
+            device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
+            mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
+                    device.getDevice());
+        });
+    }
+
+    /**
+     * Unregisters callbacks and listeners, this should be called when no longer needs to listen to
+     * events.
+     */
+    public void stop() {
+        mEventManager.unregisterCallback(this);
+        mLocalDataManager.stop();
+        mCachedDevices.forEach(device -> {
+            device.unregisterCallback(this);
+            mVolumeController.unregisterCallback(device.getDevice());
+        });
+    }
+
+    /**
+     * Loads all devices in the same set with {@code cachedDevice} and create corresponding sliders.
+     *
+     * <p>If the devices has valid ambient control points, the ambient volume UI will be visible.
+     * @param cachedDevice the remote device
+     */
+    public void loadDevice(CachedBluetoothDevice cachedDevice) {
+        if (DEBUG) {
+            Log.d(TAG, "loadDevice, device=" + cachedDevice);
+        }
+        mCachedDevice = cachedDevice;
+        mSideToDeviceMap.clear();
+        mCachedDevices.clear();
+        boolean deviceSupportVcp =
+                cachedDevice != null && cachedDevice.getProfiles().stream().anyMatch(
+                        p -> p instanceof VolumeControlProfile);
+        if (!deviceSupportVcp) {
+            mAmbientLayout.setVisible(false);
+            return;
+        }
+
+        // load devices in the same set
+        if (VALID_SIDES.contains(cachedDevice.getDeviceSide())
+                && cachedDevice.getBondState() == BOND_BONDED) {
+            mSideToDeviceMap.put(cachedDevice.getDeviceSide(), cachedDevice.getDevice());
+            mCachedDevices.add(cachedDevice);
+        }
+        for (CachedBluetoothDevice memberDevice : cachedDevice.getMemberDevice()) {
+            if (VALID_SIDES.contains(memberDevice.getDeviceSide())
+                    && memberDevice.getBondState() == BOND_BONDED) {
+                mSideToDeviceMap.put(memberDevice.getDeviceSide(), memberDevice.getDevice());
+                mCachedDevices.add(memberDevice);
+            }
+        }
+
+        mAmbientLayout.setExpandable(mSideToDeviceMap.size() >  1);
+        mAmbientLayout.setupSliders(mSideToDeviceMap);
+        refresh();
+    }
+
+    /** Refreshes the ambient volume UI. */
+    public void refresh() {
+        if (isAmbientControlAvailable()) {
+            mAmbientLayout.setVisible(true);
+            loadRemoteDataToUi();
+        } else {
+            mAmbientLayout.setVisible(false);
+        }
+    }
+
+    /** Sets if the ambient volume UI should be visible when local ambient data exist. */
+    public void setShowUiWhenLocalDataExist(boolean shouldShow) {
+        mShowUiWhenLocalDataExist = shouldShow;
+    }
+
+    /** Updates the ambient sliders according to current state. */
+    private void updateSliderUi() {
+        boolean isAnySliderEnabled = false;
+        for (Map.Entry<Integer, BluetoothDevice> entry : mSideToDeviceMap.entrySet()) {
+            final int side = entry.getKey();
+            final BluetoothDevice device = entry.getValue();
+            final boolean enabled = isDeviceConnectedToVcp(device)
+                    && mVolumeController.isAmbientControlAvailable(device);
+            isAnySliderEnabled |= enabled;
+            mAmbientLayout.setSliderEnabled(side, enabled);
+        }
+        mAmbientLayout.setSliderEnabled(SIDE_UNIFIED, isAnySliderEnabled);
+        mAmbientLayout.updateLayout();
+    }
+
+    /** Sets the ambient to the corresponding control slider. */
+    private void setVolumeIfValid(int side, int volume) {
+        if (volume == INVALID_VOLUME) {
+            return;
+        }
+        mAmbientLayout.setSliderValue(side, volume);
+        // Update new value to local data
+        if (side == SIDE_UNIFIED) {
+            mSideToDeviceMap.forEach((s, d) -> mLocalDataManager.updateGroupAmbient(d, volume));
+        } else {
+            mLocalDataManager.updateAmbient(mSideToDeviceMap.get(side), volume);
+        }
+        mLocalDataManager.flush();
+    }
+
+    private void loadLocalDataToUi() {
+        mSideToDeviceMap.forEach((s, d) -> loadLocalDataToUi(d));
+    }
+
+    private void loadLocalDataToUi(BluetoothDevice device) {
+        final HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(device);
+        if (DEBUG) {
+            Log.d(TAG, "loadLocalDataToUi, data=" + data + ", device=" + device);
+        }
+        if (isDeviceConnectedToVcp(device) && !mAmbientLayout.isMuted()) {
+            final int side = mSideToDeviceMap.inverse().getOrDefault(device, SIDE_INVALID);
+            setVolumeIfValid(side, data.ambient());
+            setVolumeIfValid(SIDE_UNIFIED, data.groupAmbient());
+        }
+        setAmbientControlExpanded(data.ambientControlExpanded());
+        updateSliderUi();
+    }
+
+    private void loadRemoteDataToUi() {
+        BluetoothDevice leftDevice = mSideToDeviceMap.get(SIDE_LEFT);
+        AmbientVolumeController.RemoteAmbientState leftState =
+                mVolumeController.refreshAmbientState(leftDevice);
+        BluetoothDevice rightDevice = mSideToDeviceMap.get(SIDE_RIGHT);
+        AmbientVolumeController.RemoteAmbientState rightState =
+                mVolumeController.refreshAmbientState(rightDevice);
+        if (DEBUG) {
+            Log.d(TAG, "loadRemoteDataToUi, left=" + leftState + ", right=" + rightState);
+        }
+        mSideToDeviceMap.forEach((side, device) -> {
+            int ambientMax = mVolumeController.getAmbientMax(device);
+            int ambientMin = mVolumeController.getAmbientMin(device);
+            if (ambientMin != ambientMax) {
+                mAmbientLayout.setSliderRange(side, ambientMin, ambientMax);
+                mAmbientLayout.setSliderRange(SIDE_UNIFIED, ambientMin, ambientMax);
+            }
+        });
+
+        // Update ambient volume
+        final int leftAmbient = leftState != null ? leftState.gainSetting() : INVALID_VOLUME;
+        final int rightAmbient = rightState != null ? rightState.gainSetting() : INVALID_VOLUME;
+        if (mAmbientLayout.isExpanded()) {
+            setVolumeIfValid(SIDE_LEFT, leftAmbient);
+            setVolumeIfValid(SIDE_RIGHT, rightAmbient);
+        } else {
+            if (leftAmbient != rightAmbient && leftAmbient != INVALID_VOLUME
+                    && rightAmbient != INVALID_VOLUME) {
+                setVolumeIfValid(SIDE_LEFT, leftAmbient);
+                setVolumeIfValid(SIDE_RIGHT, rightAmbient);
+                setAmbientControlExpanded(true);
+            } else {
+                int unifiedAmbient = leftAmbient != INVALID_VOLUME ? leftAmbient : rightAmbient;
+                setVolumeIfValid(SIDE_UNIFIED, unifiedAmbient);
+            }
+        }
+        // Initialize local data between side and group value
+        initLocalAmbientDataIfNeeded();
+
+        // Update mute state
+        boolean mutable = true;
+        boolean muted = true;
+        if (isDeviceConnectedToVcp(leftDevice) && leftState != null) {
+            mutable &= leftState.isMutable();
+            muted &= leftState.isMuted();
+        }
+        if (isDeviceConnectedToVcp(rightDevice) && rightState != null) {
+            mutable &= rightState.isMutable();
+            muted &= rightState.isMuted();
+        }
+        mAmbientLayout.setMutable(mutable);
+        mAmbientLayout.setMuted(muted);
+
+        // Ensure remote device mute state is synced
+        syncMuteStateIfNeeded(leftDevice, leftState, muted);
+        syncMuteStateIfNeeded(rightDevice, rightState, muted);
+
+        updateSliderUi();
+    }
+
+    private void setAmbientControlExpanded(boolean expanded) {
+        mAmbientLayout.setExpanded(expanded);
+        mSideToDeviceMap.forEach((s, d) -> {
+            // Update new value to local data
+            mLocalDataManager.updateAmbientControlExpanded(d, expanded);
+        });
+        mLocalDataManager.flush();
+    }
+
+    /** Checks if any device in the same set has valid ambient control points */
+    private boolean isAmbientControlAvailable() {
+        for (BluetoothDevice device : mSideToDeviceMap.values()) {
+            if (mShowUiWhenLocalDataExist) {
+                // Found local ambient data
+                if (mLocalDataManager.get(device).hasAmbientData()) {
+                    return true;
+                }
+            }
+            // Found remote ambient control points
+            if (mVolumeController.isAmbientControlAvailable(device)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void initLocalAmbientDataIfNeeded() {
+        int smallerVolumeAmongGroup = Integer.MAX_VALUE;
+        for (BluetoothDevice device : mSideToDeviceMap.values()) {
+            HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(device);
+            if (data.ambient() != INVALID_VOLUME) {
+                smallerVolumeAmongGroup = Math.min(data.ambient(), smallerVolumeAmongGroup);
+            } else if (data.groupAmbient() != INVALID_VOLUME) {
+                // Initialize side ambient from group ambient value
+                mLocalDataManager.updateAmbient(device, data.groupAmbient());
+            }
+        }
+        if (smallerVolumeAmongGroup != Integer.MAX_VALUE) {
+            for (BluetoothDevice device : mSideToDeviceMap.values()) {
+                HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(device);
+                if (data.groupAmbient() == INVALID_VOLUME) {
+                    // Initialize group ambient from smaller side ambient value
+                    mLocalDataManager.updateGroupAmbient(device, smallerVolumeAmongGroup);
+                }
+            }
+        }
+        mLocalDataManager.flush();
+    }
+
+    private void syncMuteStateIfNeeded(@Nullable BluetoothDevice device,
+            @Nullable AmbientVolumeController.RemoteAmbientState state, boolean muted) {
+        if (isDeviceConnectedToVcp(device) && state != null && state.isMutable()) {
+            if (state.isMuted() != muted) {
+                mVolumeController.setMuted(device, muted);
+            }
+        }
+    }
+
+    private boolean isDeviceConnectedToVcp(@Nullable BluetoothDevice device) {
+        return device != null && device.isConnected()
+                && mProfileManager.getVolumeControlProfile().getConnectionStatus(device)
+                == BluetoothProfile.STATE_CONNECTED;
+    }
+
+    private void postOnMainThread(Runnable runnable) {
+        mContext.getMainThreadHandler().post(runnable);
+    }
+
+    private void postDelayedOnMainThread(Runnable runnable, long delay) {
+        mContext.getMainThreadHandler().postDelayed(runnable, delay);
+    }
+
+    private void showErrorToast(int stringResId) {
+        Toast.makeText(mContext, stringResId, Toast.LENGTH_SHORT).show();
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 429e4c9..0c642d7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -656,7 +656,8 @@
     @WorkerThread
     public static boolean isAudioSharingHysteresisModeFixAvailable(@Nullable Context context) {
         return (audioSharingHysteresisModeFix() && Flags.enableLeAudioSharing())
-                || (context != null && isAudioSharingPreviewEnabled(context.getContentResolver()));
+                || (context != null && Flags.audioSharingDeveloperOption()
+                && getAudioSharingPreviewValue(context.getContentResolver()));
     }
 
     /** Returns if the le audio sharing is enabled. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
index 6725558..3cd3732 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
@@ -148,6 +148,14 @@
         }
     }
 
+    /** Flushes the data into Settings . */
+    public synchronized void flush() {
+        if (!mIsStarted) {
+            return;
+        }
+        putAmbientVolumeSettings();
+    }
+
     /**
      * Puts the local data of the corresponding hearing device.
      *
@@ -274,9 +282,6 @@
             notifyIfDataChanged(mAddrToDataMap, updatedAddrToDataMap);
             mAddrToDataMap.clear();
             mAddrToDataMap.putAll(updatedAddrToDataMap);
-            if (DEBUG) {
-                Log.v(TAG, "getLocalDataFromSettings, " + mAddrToDataMap + ", manager: " + this);
-            }
         }
     }
 
@@ -287,12 +292,10 @@
                 builder.append(KEY_ADDR).append("=").append(entry.getKey());
                 builder.append(entry.getValue().toSettingsFormat()).append(";");
             }
-            if (DEBUG) {
-                Log.v(TAG, "putAmbientVolumeSettings, " + builder + ", manager: " + this);
-            }
-            Settings.Global.putStringForUser(mContext.getContentResolver(),
-                    LOCAL_AMBIENT_VOLUME_SETTINGS, builder.toString(),
-                    UserHandle.USER_SYSTEM);
+            ThreadUtils.postOnBackgroundThread(() -> {
+                Settings.Global.putStringForUser(mContext.getContentResolver(),
+                        LOCAL_AMBIENT_VOLUME_SETTINGS, builder.toString(), UserHandle.USER_SYSTEM);
+            });
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index b52ed42..2c99a2d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -101,6 +101,7 @@
     public @interface BroadcastState {}
 
     private static final String SETTINGS_PKG = "com.android.settings";
+    private static final String SYSUI_PKG = "com.android.systemui";
     private static final String TAG = "LocalBluetoothLeBroadcast";
     private static final boolean DEBUG = BluetoothUtils.D;
 
@@ -216,6 +217,7 @@
                     }
                     setLatestBroadcastId(broadcastId);
                     setAppSourceName(mNewAppSourceName, /* updateContentResolver= */ true);
+                    notifyBroadcastStateChange(BROADCAST_STATE_ON);
                 }
 
                 @Override
@@ -232,7 +234,6 @@
                         Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId);
                     }
                     setLatestBluetoothLeBroadcastMetadata(metadata);
-                    notifyBroadcastStateChange(BROADCAST_STATE_ON);
                 }
 
                 @Override
@@ -1247,8 +1248,9 @@
     }
 
     private void notifyBroadcastStateChange(@BroadcastState int state) {
-        if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
-            Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
+        String packageName = mContext.getPackageName();
+        if (!packageName.equals(SETTINGS_PKG) && !packageName.equals(SYSUI_PKG)) {
+            Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings or SystemUI.");
             return;
         }
         if (isWorkProfile(mContext)) {
@@ -1257,8 +1259,8 @@
         }
         Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE);
         intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, state);
-        intent.setPackage(mContext.getPackageName());
-        Log.d(TAG, "notifyBroadcastStateChange for state = " + state);
+        intent.setPackage(SETTINGS_PKG);
+        Log.d(TAG, "notifyBroadcastStateChange for state = " + state + " by pkg = " + packageName);
         mContext.sendBroadcast(intent);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
index e01f279..c71b19c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
@@ -501,7 +501,7 @@
                 val wifiManager = context.getSystemService(WifiManager::class.java) ?: return@launch
                 val aapmManager = context.getSystemService(AdvancedProtectionManager::class.java)
                 if (isAdvancedProtectionEnabled(aapmManager)) {
-                    val intent = aapmManager.createSupportIntent(
+                    val intent = AdvancedProtectionManager.createSupportIntent(
                         AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP,
                         AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION)
                     onStartActivity(intent)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeUiControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeUiControllerTest.java
new file mode 100644
index 0000000..8b606e2
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeUiControllerTest.java
@@ -0,0 +1,315 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import static android.bluetooth.AudioInputControl.MUTE_DISABLED;
+import static android.bluetooth.AudioInputControl.MUTE_MUTED;
+import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
+import static android.bluetooth.BluetoothDevice.BOND_BONDED;
+
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/** Tests for {@link AmbientVolumeUiController}. */
+@RunWith(RobolectricTestRunner.class)
+public class AmbientVolumeUiControllerTest {
+
+    @Rule
+    public MockitoRule mockito = MockitoJUnit.rule();
+
+    private static final String TEST_ADDRESS = "00:00:00:00:11";
+    private static final String TEST_MEMBER_ADDRESS = "00:00:00:00:22";
+
+    @Mock
+    LocalBluetoothManager mBluetoothManager;
+    @Mock
+    LocalBluetoothProfileManager mProfileManager;
+    @Mock
+    BluetoothEventManager mEventManager;
+    @Mock
+    VolumeControlProfile mVolumeControlProfile;
+    @Mock
+    AmbientVolumeUi mAmbientLayout;
+    @Mock
+    private AmbientVolumeController mVolumeController;
+    @Mock
+    private HearingDeviceLocalDataManager mLocalDataManager;
+    @Mock
+    private CachedBluetoothDevice mCachedDevice;
+    @Mock
+    private CachedBluetoothDevice mCachedMemberDevice;
+    @Mock
+    private BluetoothDevice mDevice;
+    @Mock
+    private BluetoothDevice mMemberDevice;
+    @Mock
+    private Handler mTestHandler;
+
+    @Spy
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private AmbientVolumeUiController mController;
+
+    @Before
+    public void setUp() {
+        when(mBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
+        when(mBluetoothManager.getEventManager()).thenReturn(mEventManager);
+
+        mController = spy(new AmbientVolumeUiController(mContext, mBluetoothManager,
+                mAmbientLayout, mVolumeController, mLocalDataManager));
+
+        when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControlProfile);
+        when(mVolumeControlProfile.getConnectionStatus(mDevice)).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        when(mVolumeControlProfile.getConnectionStatus(mMemberDevice)).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        when(mVolumeController.isAmbientControlAvailable(mDevice)).thenReturn(true);
+        when(mVolumeController.isAmbientControlAvailable(mMemberDevice)).thenReturn(true);
+        when(mLocalDataManager.get(any(BluetoothDevice.class))).thenReturn(
+                new HearingDeviceLocalDataManager.Data.Builder().build());
+
+        when(mContext.getMainThreadHandler()).thenReturn(mTestHandler);
+        Answer<Object> answer = invocationOnMock -> {
+            invocationOnMock.getArgument(0, Runnable.class).run();
+            return null;
+        };
+        when(mTestHandler.post(any(Runnable.class))).thenAnswer(answer);
+        when(mTestHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(answer);
+
+        prepareDevice(/* hasMember= */ true);
+        mController.loadDevice(mCachedDevice);
+        Mockito.reset(mController);
+        Mockito.reset(mAmbientLayout);
+    }
+
+    @Test
+    public void loadDevice_deviceWithoutMember_controlNotExpandable() {
+        prepareDevice(/* hasMember= */ false);
+
+        mController.loadDevice(mCachedDevice);
+
+        verify(mAmbientLayout).setExpandable(false);
+    }
+
+    @Test
+    public void loadDevice_deviceWithMember_controlExpandable() {
+        prepareDevice(/* hasMember= */ true);
+
+        mController.loadDevice(mCachedDevice);
+
+        verify(mAmbientLayout).setExpandable(true);
+    }
+
+    @Test
+    public void loadDevice_deviceNotSupportVcp_ambientLayoutGone() {
+        when(mCachedDevice.getProfiles()).thenReturn(List.of());
+
+        mController.loadDevice(mCachedDevice);
+
+        verify(mAmbientLayout).setVisible(false);
+    }
+
+    @Test
+    public void loadDevice_ambientControlNotAvailable_ambientLayoutGone() {
+        when(mVolumeController.isAmbientControlAvailable(mDevice)).thenReturn(false);
+        when(mVolumeController.isAmbientControlAvailable(mMemberDevice)).thenReturn(false);
+
+        mController.loadDevice(mCachedDevice);
+
+        verify(mAmbientLayout).setVisible(false);
+    }
+
+    @Test
+    public void loadDevice_supportVcpAndAmbientControlAvailable_ambientLayoutVisible() {
+        when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
+        when(mVolumeController.isAmbientControlAvailable(mDevice)).thenReturn(true);
+
+        mController.loadDevice(mCachedDevice);
+
+        verify(mAmbientLayout).setVisible(true);
+    }
+
+    @Test
+    public void start_callbackRegistered() {
+        mController.start();
+
+        verify(mEventManager).registerCallback(mController);
+        verify(mLocalDataManager).start();
+        verify(mVolumeController).registerCallback(any(Executor.class), eq(mDevice));
+        verify(mVolumeController).registerCallback(any(Executor.class), eq(mMemberDevice));
+        verify(mCachedDevice).registerCallback(any(Executor.class),
+                any(CachedBluetoothDevice.Callback.class));
+        verify(mCachedMemberDevice).registerCallback(any(Executor.class),
+                any(CachedBluetoothDevice.Callback.class));
+    }
+
+    @Test
+    public void stop_callbackUnregistered() {
+        mController.stop();
+
+        verify(mEventManager).unregisterCallback(mController);
+        verify(mLocalDataManager).stop();
+        verify(mVolumeController).unregisterCallback(mDevice);
+        verify(mVolumeController).unregisterCallback(mMemberDevice);
+        verify(mCachedDevice).unregisterCallback(any(CachedBluetoothDevice.Callback.class));
+        verify(mCachedMemberDevice).unregisterCallback(any(CachedBluetoothDevice.Callback.class));
+    }
+
+    @Test
+    public void onDeviceLocalDataChange_verifySetExpandedAndDataUpdated() {
+        final boolean testExpanded = true;
+        HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
+                .ambient(0).groupAmbient(0).ambientControlExpanded(testExpanded).build();
+        when(mLocalDataManager.get(mDevice)).thenReturn(data);
+
+        mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mAmbientLayout).setExpanded(testExpanded);
+        verifyDeviceDataUpdated(mDevice);
+    }
+
+    @Test
+    public void onAmbientChanged_refreshWhenNotInitiateFromUi() {
+        HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
+                .ambient(10).groupAmbient(10).ambientControlExpanded(true).build();
+        when(mLocalDataManager.get(mDevice)).thenReturn(data);
+        when(mAmbientLayout.isExpanded()).thenReturn(true);
+
+        mController.onAmbientChanged(mDevice, 10);
+        verify(mController, never()).refresh();
+
+        mController.onAmbientChanged(mDevice, 20);
+        verify(mController).refresh();
+    }
+
+    @Test
+    public void onMuteChanged_refreshWhenNotInitiateFromUi() {
+        AmbientVolumeController.RemoteAmbientState state =
+                new AmbientVolumeController.RemoteAmbientState(MUTE_NOT_MUTED, 0);
+        when(mVolumeController.refreshAmbientState(mDevice)).thenReturn(state);
+        when(mAmbientLayout.isExpanded()).thenReturn(false);
+
+        mController.onMuteChanged(mDevice, MUTE_NOT_MUTED);
+        verify(mController, never()).refresh();
+
+        mController.onMuteChanged(mDevice, MUTE_MUTED);
+        verify(mController).refresh();
+    }
+
+    @Test
+    public void refresh_leftAndRightDifferentGainSetting_expandControl() {
+        prepareRemoteData(mDevice, 10, MUTE_NOT_MUTED);
+        prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
+        when(mAmbientLayout.isExpanded()).thenReturn(false);
+
+        mController.refresh();
+
+        verify(mAmbientLayout).setExpanded(true);
+    }
+
+    @Test
+    public void refresh_oneSideNotMutable_controlNotMutableAndNotMuted() {
+        prepareRemoteData(mDevice, 10, MUTE_DISABLED);
+        prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
+
+        mController.refresh();
+
+        verify(mAmbientLayout).setMutable(false);
+        verify(mAmbientLayout).setMuted(false);
+    }
+
+    @Test
+    public void refresh_oneSideNotMuted_controlNotMutedAndSyncToRemote() {
+        prepareRemoteData(mDevice, 10, MUTE_MUTED);
+        prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
+
+        mController.refresh();
+
+        verify(mAmbientLayout).setMutable(true);
+        verify(mAmbientLayout).setMuted(false);
+        verify(mVolumeController).setMuted(mDevice, false);
+    }
+
+    private void prepareDevice(boolean hasMember) {
+        when(mCachedDevice.getDeviceSide()).thenReturn(SIDE_LEFT);
+        when(mCachedDevice.getDevice()).thenReturn(mDevice);
+        when(mCachedDevice.getBondState()).thenReturn(BOND_BONDED);
+        when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
+        when(mDevice.getAddress()).thenReturn(TEST_ADDRESS);
+        when(mDevice.getAnonymizedAddress()).thenReturn(TEST_ADDRESS);
+        when(mDevice.isConnected()).thenReturn(true);
+        if (hasMember) {
+            when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedMemberDevice));
+            when(mCachedMemberDevice.getDeviceSide()).thenReturn(SIDE_RIGHT);
+            when(mCachedMemberDevice.getDevice()).thenReturn(mMemberDevice);
+            when(mCachedMemberDevice.getBondState()).thenReturn(BOND_BONDED);
+            when(mCachedMemberDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
+            when(mMemberDevice.getAddress()).thenReturn(TEST_MEMBER_ADDRESS);
+            when(mMemberDevice.getAnonymizedAddress()).thenReturn(TEST_MEMBER_ADDRESS);
+            when(mMemberDevice.isConnected()).thenReturn(true);
+        } else {
+            when(mCachedDevice.getMemberDevice()).thenReturn(Set.of());
+        }
+    }
+
+    private void prepareRemoteData(BluetoothDevice device, int gainSetting, int mute) {
+        when(mVolumeController.refreshAmbientState(device)).thenReturn(
+                new AmbientVolumeController.RemoteAmbientState(gainSetting, mute));
+    }
+
+    private void verifyDeviceDataUpdated(BluetoothDevice device) {
+        verify(mLocalDataManager).updateAmbient(eq(device), anyInt());
+        verify(mLocalDataManager).updateGroupAmbient(eq(device), anyInt());
+        verify(mLocalDataManager).updateAmbientControlExpanded(eq(device),
+                anyBoolean());
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index fa5d542..ab9f871 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -970,8 +970,10 @@
         when(cachedBluetoothDevice2.getGroupId()).thenReturn(2);
 
         BluetoothDevice device1 = mock(BluetoothDevice.class);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(device1);
         when(mDeviceManager.findDevice(device1)).thenReturn(mCachedBluetoothDevice);
         BluetoothDevice device2 = mock(BluetoothDevice.class);
+        when(cachedBluetoothDevice2.getDevice()).thenReturn(device2);
         when(mDeviceManager.findDevice(device2)).thenReturn(cachedBluetoothDevice2);
 
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(device1, device2));
@@ -991,8 +993,10 @@
         when(cachedBluetoothDevice2.getGroupId()).thenReturn(2);
 
         BluetoothDevice device1 = mock(BluetoothDevice.class);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(device1);
         when(mDeviceManager.findDevice(device1)).thenReturn(mCachedBluetoothDevice);
         BluetoothDevice device2 = mock(BluetoothDevice.class);
+        when(cachedBluetoothDevice2.getDevice()).thenReturn(device2);
         when(mDeviceManager.findDevice(device2)).thenReturn(cachedBluetoothDevice2);
 
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(device1, device2));
@@ -1012,8 +1016,10 @@
         when(cachedBluetoothDevice2.getGroupId()).thenReturn(2);
 
         BluetoothDevice device1 = mock(BluetoothDevice.class);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(device1);
         when(mDeviceManager.findDevice(device1)).thenReturn(mCachedBluetoothDevice);
         BluetoothDevice device2 = mock(BluetoothDevice.class);
+        when(cachedBluetoothDevice2.getDevice()).thenReturn(device2);
         when(mDeviceManager.findDevice(device2)).thenReturn(cachedBluetoothDevice2);
 
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(device2));
@@ -1035,10 +1041,13 @@
         when(cachedBluetoothDevice3.getGroupId()).thenReturn(3);
 
         BluetoothDevice device1 = mock(BluetoothDevice.class);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(device1);
         when(mDeviceManager.findDevice(device1)).thenReturn(mCachedBluetoothDevice);
         BluetoothDevice device2 = mock(BluetoothDevice.class);
+        when(cachedBluetoothDevice2.getDevice()).thenReturn(device2);
         when(mDeviceManager.findDevice(device2)).thenReturn(cachedBluetoothDevice2);
         BluetoothDevice device3 = mock(BluetoothDevice.class);
+        when(cachedBluetoothDevice3.getDevice()).thenReturn(device3);
         when(mDeviceManager.findDevice(device3)).thenReturn(cachedBluetoothDevice3);
 
         when(mAssistant.getAllConnectedDevices())
@@ -1280,6 +1289,8 @@
         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
         mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 1);
 
         assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isTrue();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
index 6d83588..6485636 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
@@ -31,6 +31,8 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.settingslib.utils.ThreadUtils;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -49,7 +51,10 @@
 
 /** Tests for {@link HearingDeviceLocalDataManager}. */
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {HearingDeviceLocalDataManagerTest.ShadowGlobal.class})
+@Config(shadows = {
+        HearingDeviceLocalDataManagerTest.ShadowGlobal.class,
+        HearingDeviceLocalDataManagerTest.ShadowThreadUtils.class,
+})
 public class HearingDeviceLocalDataManagerTest {
 
     private static final String TEST_ADDRESS = "XX:XX:XX:XX:11:22";
@@ -249,4 +254,12 @@
             return sDataMap.computeIfAbsent(cr, k -> new HashMap<>());
         }
     }
+
+    @Implements(value = ThreadUtils.class)
+    public static class ShadowThreadUtils {
+        @Implementation
+        protected static void postOnBackgroundThread(Runnable runnable) {
+            runnable.run();
+        }
+    }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index fb4293a..46bd88f 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -994,6 +994,9 @@
     <uses-permission android:name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"
         android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/>
 
+    <!-- Permission required for CTS test - CtsContentProviderMultiUserTest -->
+    <uses-permission android:name="android.permission.RESOLVE_COMPONENT_FOR_UID" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5ff2d1b..e02bd9e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1904,3 +1904,10 @@
    description: "Invokes edit mode directly from long press in glanceable hub"
    bug: "382531177"
 }
+
+flag {
+   name: "notification_magic_actions_treatment"
+   namespace: "systemui"
+   description: "Special UI treatment for magic actions"
+   bug: "383567383"
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 9744424..141736f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -29,6 +29,7 @@
 import com.android.compose.nestedscroll.ScrollController
 import com.android.compose.ui.util.SpaceVectorConverter
 import kotlin.math.absoluteValue
+import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.NonCancellable
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -359,13 +360,24 @@
             return swipeAnimation.animateOffset(velocity, targetContent)
         }
 
-        overscrollEffect.applyToFling(
-            velocity = velocity.toVelocity(),
-            performFling = {
-                val velocityLeft = it.toFloat()
-                swipeAnimation.animateOffset(velocityLeft, targetContent).toVelocity()
-            },
-        )
+        val overscrollCompletable = CompletableDeferred<Unit>()
+        try {
+            overscrollEffect.applyToFling(
+                velocity = velocity.toVelocity(),
+                performFling = {
+                    val velocityLeft = it.toFloat()
+                    swipeAnimation
+                        .animateOffset(
+                            velocityLeft,
+                            targetContent,
+                            overscrollCompletable = overscrollCompletable,
+                        )
+                        .toVelocity()
+                },
+            )
+        } finally {
+            overscrollCompletable.complete(Unit)
+        }
 
         return velocity
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt
index 599a152a..167928b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt
@@ -30,7 +30,8 @@
     // the transition is running. If the [renderAuthority.size] is 1 it means that that this element
     // is currently composed only in one nesting level, which means that the render authority
     // is determined by "classic" shared element code.
-    return renderAuthority.size == 1 || renderAuthority.first() == content
+    return renderAuthority.size > 0 &&
+        (renderAuthority.size == 1 || renderAuthority.first() == content)
 }
 
 /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 5aaeda8..47daa76 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -271,7 +271,7 @@
 
     /** The offset animation that animates the offset once the user lifts their finger. */
     private var offsetAnimation: Animatable<Float, AnimationVector1D>? by mutableStateOf(null)
-    private val offsetAnimationRunnable = CompletableDeferred<(suspend () -> Unit)?>()
+    private val offsetAnimationRunnable = CompletableDeferred<suspend () -> Unit>()
 
     val isUserInputOngoing: Boolean
         get() = offsetAnimation == null
@@ -333,6 +333,7 @@
         initialVelocity: Float,
         targetContent: T,
         spec: AnimationSpec<Float>? = null,
+        overscrollCompletable: CompletableDeferred<Unit>? = null,
     ): Float {
         check(!isAnimatingOffset()) { "SwipeAnimation.animateOffset() can only be called once" }
 
@@ -391,7 +392,12 @@
         // detail).
         if (skipAnimation) {
             // Unblock the job.
-            offsetAnimationRunnable.complete(null)
+            offsetAnimationRunnable.complete {
+                // Wait for overscroll to finish so that the transition is removed from the STLState
+                // only after the overscroll is done, to avoid dropping frame right when the user
+                // lifts their finger and overscroll is animated to 0.
+                overscrollCompletable?.await()
+            }
             return 0f
         }
 
@@ -450,6 +456,11 @@
                     // The animation consumed the whole available velocity
                     velocityConsumed.complete(initialVelocity)
                 }
+
+                // Wait for overscroll to finish so that the transition is removed from the STLState
+                // only after the overscroll is done, to avoid dropping frame right when the user
+                // lifts their finger and overscroll is animated to 0.
+                overscrollCompletable?.await()
             }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayoutTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayoutTest.java
new file mode 100644
index 0000000..455329f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayoutTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+import static com.android.systemui.accessibility.hearingaid.AmbientVolumeLayout.ROTATION_COLLAPSED;
+import static com.android.systemui.accessibility.hearingaid.AmbientVolumeLayout.ROTATION_EXPANDED;
+import static com.android.systemui.accessibility.hearingaid.AmbientVolumeLayout.SIDE_UNIFIED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.bluetooth.AmbientVolumeUi;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Map;
+
+/** Tests for {@link AmbientVolumeLayout}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AmbientVolumeLayoutTest extends SysuiTestCase {
+
+    private static final int TEST_LEFT_VOLUME_LEVEL = 1;
+    private static final int TEST_RIGHT_VOLUME_LEVEL = 2;
+    private static final int TEST_UNIFIED_VOLUME_LEVEL = 3;
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Spy
+    private Context mContext = ApplicationProvider.getApplicationContext();
+    @Mock
+    private AmbientVolumeUi.AmbientVolumeUiListener mListener;
+
+    private AmbientVolumeLayout mLayout;
+    private ImageView mExpandIcon;
+    private ImageView mVolumeIcon;
+    private final Map<Integer, BluetoothDevice> mSideToDeviceMap = new ArrayMap<>();
+
+    @Before
+    public void setUp() {
+        mLayout = new AmbientVolumeLayout(mContext);
+        mLayout.setListener(mListener);
+        mLayout.setExpandable(true);
+        mLayout.setMutable(true);
+
+        prepareDevices();
+        mLayout.setupSliders(mSideToDeviceMap);
+        mLayout.getSliders().forEach((side, slider) -> {
+            slider.setMin(0);
+            slider.setMax(4);
+            if (side == SIDE_LEFT) {
+                slider.setValue(TEST_LEFT_VOLUME_LEVEL);
+            } else if (side == SIDE_RIGHT) {
+                slider.setValue(TEST_RIGHT_VOLUME_LEVEL);
+            } else if (side == SIDE_UNIFIED) {
+                slider.setValue(TEST_UNIFIED_VOLUME_LEVEL);
+            }
+        });
+
+        mExpandIcon = mLayout.getExpandIcon();
+        mVolumeIcon = mLayout.getVolumeIcon();
+    }
+
+    @Test
+    public void setExpandable_expandable_expandIconVisible() {
+        mLayout.setExpandable(true);
+
+        assertThat(mExpandIcon.getVisibility()).isEqualTo(VISIBLE);
+    }
+
+    @Test
+    public void setExpandable_notExpandable_expandIconGone() {
+        mLayout.setExpandable(false);
+
+        assertThat(mExpandIcon.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void setExpanded_expanded_assertControlUiCorrect() {
+        mLayout.setExpanded(true);
+
+        assertControlUiCorrect();
+    }
+
+    @Test
+    public void setExpanded_notExpanded_assertControlUiCorrect() {
+        mLayout.setExpanded(false);
+
+        assertControlUiCorrect();
+    }
+
+    @Test
+    public void setMutable_mutable_clickOnMuteIconChangeMuteState() {
+        mLayout.setMutable(true);
+        mLayout.setMuted(false);
+
+        mVolumeIcon.callOnClick();
+
+        assertThat(mLayout.isMuted()).isTrue();
+    }
+
+    @Test
+    public void setMutable_notMutable_clickOnMuteIconWontChangeMuteState() {
+        mLayout.setMutable(false);
+        mLayout.setMuted(false);
+
+        mVolumeIcon.callOnClick();
+
+        assertThat(mLayout.isMuted()).isFalse();
+    }
+
+    @Test
+    public void updateLayout_mute_volumeIconIsCorrect() {
+        mLayout.setMuted(true);
+        mLayout.updateLayout();
+
+        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(0);
+    }
+
+    @Test
+    public void updateLayout_unmuteAndExpanded_volumeIconIsCorrect() {
+        mLayout.setMuted(false);
+        mLayout.setExpanded(true);
+        mLayout.updateLayout();
+
+        int expectedLevel = calculateVolumeLevel(TEST_LEFT_VOLUME_LEVEL, TEST_RIGHT_VOLUME_LEVEL);
+        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
+    }
+
+    @Test
+    public void updateLayout_unmuteAndNotExpanded_volumeIconIsCorrect() {
+        mLayout.setMuted(false);
+        mLayout.setExpanded(false);
+        mLayout.updateLayout();
+
+        int expectedLevel = calculateVolumeLevel(TEST_UNIFIED_VOLUME_LEVEL,
+                TEST_UNIFIED_VOLUME_LEVEL);
+        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
+    }
+
+    @Test
+    public void setSliderEnabled_expandedAndLeftIsDisabled_volumeIconIsCorrect() {
+        mLayout.setExpanded(true);
+        mLayout.setSliderEnabled(SIDE_LEFT, false);
+
+        int expectedLevel = calculateVolumeLevel(0, TEST_RIGHT_VOLUME_LEVEL);
+        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
+    }
+
+    @Test
+    public void setSliderValue_expandedAndLeftValueChanged_volumeIconIsCorrect() {
+        mLayout.setExpanded(true);
+        mLayout.setSliderValue(SIDE_LEFT, 4);
+
+        int expectedLevel = calculateVolumeLevel(4, TEST_RIGHT_VOLUME_LEVEL);
+        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
+    }
+
+    private int calculateVolumeLevel(int left, int right) {
+        return left * 5 + right;
+    }
+
+    private void assertControlUiCorrect() {
+        final boolean expanded = mLayout.isExpanded();
+        final Map<Integer, AmbientVolumeSlider> sliders = mLayout.getSliders();
+        if (expanded) {
+            assertThat(sliders.get(SIDE_UNIFIED).getVisibility()).isEqualTo(GONE);
+            assertThat(sliders.get(SIDE_LEFT).getVisibility()).isEqualTo(VISIBLE);
+            assertThat(sliders.get(SIDE_RIGHT).getVisibility()).isEqualTo(VISIBLE);
+            assertThat(mExpandIcon.getRotation()).isEqualTo(ROTATION_EXPANDED);
+        } else {
+            assertThat(sliders.get(SIDE_UNIFIED).getVisibility()).isEqualTo(VISIBLE);
+            assertThat(sliders.get(SIDE_LEFT).getVisibility()).isEqualTo(GONE);
+            assertThat(sliders.get(SIDE_RIGHT).getVisibility()).isEqualTo(GONE);
+            assertThat(mExpandIcon.getRotation()).isEqualTo(ROTATION_COLLAPSED);
+        }
+    }
+
+    private void prepareDevices() {
+        mSideToDeviceMap.put(SIDE_LEFT, mock(BluetoothDevice.class));
+        mSideToDeviceMap.put(SIDE_RIGHT, mock(BluetoothDevice.class));
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSliderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSliderTest.java
new file mode 100644
index 0000000..78dfda8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSliderTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Tests for {@link AmbientVolumeLayout}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AmbientVolumeSliderTest extends SysuiTestCase {
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Spy
+    private Context mContext = ApplicationProvider.getApplicationContext();
+
+    private AmbientVolumeSlider mSlider;
+
+    @Before
+    public void setUp() {
+        mSlider = new AmbientVolumeSlider(mContext);
+    }
+
+    @Test
+    public void setTitle_titleCorrect() {
+        final String testTitle = "test";
+        mSlider.setTitle(testTitle);
+
+        assertThat(mSlider.getTitle()).isEqualTo(testTitle);
+    }
+
+    @Test
+    public void getVolumeLevel_valueMin_volumeLevelIsZero() {
+        prepareSlider(/* min= */ 0, /* max= */ 100, /* value= */ 0);
+
+        // The volume level is divided into 5 levels:
+        // Level 0 corresponds to the minimum volume value. The range between the minimum and
+        // maximum volume is divided into 4 equal intervals, represented by levels 1 to 4.
+        assertThat(mSlider.getVolumeLevel()).isEqualTo(0);
+    }
+
+    @Test
+    public void getVolumeLevel_valueMax_volumeLevelIsFour() {
+        prepareSlider(/* min= */ 0, /* max= */ 100, /* value= */ 100);
+
+        assertThat(mSlider.getVolumeLevel()).isEqualTo(4);
+    }
+
+    @Test
+    public void getVolumeLevel_volumeLevelIsCorrect() {
+        prepareSlider(/* min= */ 0, /* max= */ 100, /* value= */ 73);
+
+        assertThat(mSlider.getVolumeLevel()).isEqualTo(3);
+    }
+
+    private void prepareSlider(float min, float max, float value) {
+        mSlider.setMin(min);
+        mSlider.setMax(max);
+        mSlider.setValue(value);
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index ad12c61..43d0d69c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -16,8 +16,11 @@
 
 package com.android.systemui.accessibility.hearingaid;
 
+import static android.bluetooth.BluetoothDevice.BOND_BONDED;
 import static android.bluetooth.BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
 
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
 import static com.android.systemui.accessibility.hearingaid.HearingDevicesDialogDelegate.LIVE_CAPTION_INTENT;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -31,6 +34,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.bluetooth.AudioInputControl;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHapPresetInfo;
 import android.bluetooth.BluetoothProfile;
@@ -61,6 +65,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.bluetooth.VolumeControlProfile;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
@@ -90,6 +95,7 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
+
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
@@ -120,6 +126,8 @@
     @Mock
     private HapClientProfile mHapClientProfile;
     @Mock
+    private VolumeControlProfile mVolumeControlProfile;
+    @Mock
     private CachedBluetoothDeviceManager mCachedDeviceManager;
     @Mock
     private BluetoothEventManager mBluetoothEventManager;
@@ -151,21 +159,25 @@
         when(mLocalBluetoothManager.getBluetoothAdapter()).thenReturn(mLocalBluetoothAdapter);
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
         when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
+        when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControlProfile);
         when(mLocalBluetoothAdapter.isEnabled()).thenReturn(true);
         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
         when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(List.of(mCachedDevice));
         when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
         when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
-        when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mDevice.getBondState()).thenReturn(BOND_BONDED);
         when(mDevice.isConnected()).thenReturn(true);
         when(mCachedDevice.getDevice()).thenReturn(mDevice);
         when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
         when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
-        when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
+        when(mCachedDevice.getProfiles()).thenReturn(
+                List.of(mHapClientProfile, mVolumeControlProfile));
         when(mCachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
         when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
         when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
         when(mCachedDevice.getDrawableWithDescription()).thenReturn(new Pair<>(mDrawable, ""));
+        when(mCachedDevice.getBondState()).thenReturn(BOND_BONDED);
+        when(mCachedDevice.getDeviceSide()).thenReturn(SIDE_LEFT);
         when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
 
         mContext.setMockPackageManager(mPackageManager);
@@ -292,6 +304,46 @@
     }
 
     @Test
+    @EnableFlags(com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_AMBIENT_VOLUME_CONTROL)
+    public void showDialog_deviceNotSupportVcp_ambientLayoutGone() {
+        when(mCachedDevice.getProfiles()).thenReturn(List.of());
+
+        setUpDeviceDialogWithoutPairNewDeviceButton();
+        mDialog.show();
+
+        ViewGroup ambientLayout = getAmbientLayout(mDialog);
+        assertThat(ambientLayout.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    @EnableFlags(com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_AMBIENT_VOLUME_CONTROL)
+    public void showDialog_ambientControlNotAvailable_ambientLayoutGone() {
+        when(mVolumeControlProfile.getAudioInputControlServices(mDevice)).thenReturn(List.of());
+
+        setUpDeviceDialogWithoutPairNewDeviceButton();
+        mDialog.show();
+
+        ViewGroup ambientLayout = getAmbientLayout(mDialog);
+        assertThat(ambientLayout.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    @EnableFlags(com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_AMBIENT_VOLUME_CONTROL)
+    public void showDialog_supportVcpAndAmbientControlAvailable_ambientLayoutVisible() {
+        when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
+        AudioInputControl audioInputControl = prepareAudioInputControl();
+        when(mVolumeControlProfile.getAudioInputControlServices(mDevice)).thenReturn(
+                List.of(audioInputControl));
+        when(mVolumeControlProfile.getConnectionStatus(mDevice)).thenReturn(STATE_CONNECTED);
+
+        setUpDeviceDialogWithoutPairNewDeviceButton();
+        mDialog.show();
+
+        ViewGroup ambientLayout = getAmbientLayout(mDialog);
+        assertThat(ambientLayout.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
     public void onActiveDeviceChanged_presetExist_presetSelected() {
         setUpDeviceDialogWithoutPairNewDeviceButton();
         mDialog.show();
@@ -368,6 +420,10 @@
         return dialog.requireViewById(R.id.preset_layout);
     }
 
+    private ViewGroup getAmbientLayout(SystemUIDialog dialog) {
+        return dialog.requireViewById(R.id.ambient_layout);
+    }
+
 
     private int countChildWithoutSpace(ViewGroup viewGroup) {
         int spaceCount = 0;
@@ -388,6 +444,16 @@
         assertThat(toolsLayout.getVisibility()).isEqualTo(targetVisibility);
     }
 
+    private AudioInputControl prepareAudioInputControl() {
+        AudioInputControl audioInputControl = mock(AudioInputControl.class);
+        when(audioInputControl.getAudioInputType()).thenReturn(
+                AudioInputControl.AUDIO_INPUT_TYPE_AMBIENT);
+        when(audioInputControl.getGainMode()).thenReturn(AudioInputControl.GAIN_MODE_MANUAL);
+        when(audioInputControl.getAudioInputStatus()).thenReturn(
+                AudioInputControl.AUDIO_INPUT_STATUS_ACTIVE);
+        return audioInputControl;
+    }
+
     @After
     public void reset() {
         if (mDialogDelegate != null) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 271cd3a..cbb6f81 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -168,17 +168,6 @@
     }
 
     @Test
-    fun testOnBackRequested_closeUserSwitcherIfOpen() {
-        whenever(shadeBackActionInteractor.closeUserSwitcherIfOpen()).thenReturn(true)
-
-        val result = backActionInteractor.onBackRequested()
-
-        assertTrue(result)
-        verify(statusBarKeyguardViewManager, never()).onBackPressed()
-        verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
-    }
-
-    @Test
     fun testOnBackRequested_returnsFalse() {
         // make shouldBackBeHandled return false
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModelTest.kt
index a8a3873..9271980 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModelTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.communal.widgets.GlanceableHubWidgetManager
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.backgroundCoroutineContext
 import com.android.systemui.kosmos.runCurrent
 import com.android.systemui.kosmos.runTest
@@ -57,8 +58,8 @@
 
     private val Kosmos.listenerDelegateFactory by
         Kosmos.Fixture {
-            AppWidgetHostListenerDelegate.Factory { listener ->
-                AppWidgetHostListenerDelegate(fakeExecutor, listener)
+            AppWidgetHostListenerDelegate.Factory { tag, listener ->
+                AppWidgetHostListenerDelegate(applicationCoroutineScope, tag, listener)
             }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
index 017c778..214cd1a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
@@ -27,10 +27,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.collectValues
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.SelectedUserModel
@@ -43,10 +44,6 @@
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -58,11 +55,9 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class CommunalWidgetHostTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
 
     @Mock private lateinit var appWidgetManager: AppWidgetManager
     @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
@@ -103,12 +98,11 @@
 
     @Test
     fun allocateIdAndBindWidget_withCurrentUser() =
-        testScope.runTest {
+        kosmos.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val widgetId = 1
             val userId by collectLastValue(selectedUserInteractor.selectedUser)
             selectUser()
-            runCurrent()
 
             val user = UserHandle(checkNotNull(userId))
             whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId)
@@ -129,7 +123,7 @@
 
     @Test
     fun allocateIdAndBindWidget_onSuccess() =
-        testScope.runTest {
+        kosmos.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val widgetId = 1
             val user = UserHandle(0)
@@ -152,7 +146,7 @@
 
     @Test
     fun allocateIdAndBindWidget_onFailure() =
-        testScope.runTest {
+        kosmos.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val widgetId = 1
             val user = UserHandle(0)
@@ -179,12 +173,11 @@
 
     @Test
     fun listener_exactlyOneListenerRegisteredForEachWidgetWhenHostStartListening() =
-        testScope.runTest {
+        kosmos.runTest {
             // 3 widgets registered with the host
             whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2, 3))
 
             underTest.startObservingHost()
-            runCurrent()
 
             // Make sure no listener is set before host starts listening
             verify(appWidgetHost, never()).setListener(any(), any())
@@ -195,7 +188,6 @@
                     verify(appWidgetHost).addObserver(capture())
                 }
             observer.onHostStartListening()
-            runCurrent()
 
             // Verify a listener is set for each widget
             verify(appWidgetHost, times(3)).setListener(any(), any())
@@ -206,12 +198,11 @@
 
     @Test
     fun listener_listenersRemovedWhenHostStopListening() =
-        testScope.runTest {
+        kosmos.runTest {
             // 3 widgets registered with the host
             whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2, 3))
 
             underTest.startObservingHost()
-            runCurrent()
 
             // Host starts listening
             val observer =
@@ -219,7 +210,6 @@
                     verify(appWidgetHost).addObserver(capture())
                 }
             observer.onHostStartListening()
-            runCurrent()
 
             // Verify none of the listener is removed before host stop listening
             verify(appWidgetHost, never()).removeListener(any())
@@ -235,7 +225,7 @@
 
     @Test
     fun listener_addNewListenerWhenNewIdAllocated() =
-        testScope.runTest {
+        kosmos.runTest {
             whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf())
             val observer = start()
 
@@ -251,7 +241,7 @@
 
     @Test
     fun listener_removeListenerWhenWidgetDeleted() =
-        testScope.runTest {
+        kosmos.runTest {
             whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1))
             val observer = start()
 
@@ -267,7 +257,7 @@
 
     @Test
     fun providerInfo_populatesWhenStartListening() =
-        testScope.runTest {
+        kosmos.runTest {
             whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
             whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
             whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
@@ -279,7 +269,6 @@
             assertThat(providerInfoValues[0]).isEmpty()
 
             start()
-            runCurrent()
 
             // Assert that the provider info map is populated after host started listening, and that
             // all providers are emitted at once
@@ -290,13 +279,12 @@
 
     @Test
     fun providerInfo_clearsWhenStopListening() =
-        testScope.runTest {
+        kosmos.runTest {
             whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
             whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
             whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
 
             val observer = start()
-            runCurrent()
 
             // Assert that the provider info map is populated
             val providerInfo by collectLastValue(underTest.appWidgetProviders)
@@ -312,7 +300,7 @@
 
     @Test
     fun providerInfo_onUpdate() =
-        testScope.runTest {
+        kosmos.runTest {
             whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
             whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
             whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
@@ -320,7 +308,6 @@
             val providerInfo by collectLastValue(underTest.appWidgetProviders)
 
             start()
-            runCurrent()
 
             // Assert that the provider info map is populated
             assertThat(providerInfo)
@@ -332,7 +319,6 @@
                     verify(appWidgetHost).setListener(eq(1), capture())
                 }
             listener.onUpdateProviderInfo(providerInfo3)
-            runCurrent()
 
             // Assert that the update is reflected in the flow
             assertThat(providerInfo)
@@ -341,7 +327,7 @@
 
     @Test
     fun providerInfo_updateWhenANewWidgetIsBound() =
-        testScope.runTest {
+        kosmos.runTest {
             whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
             whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
             whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
@@ -349,7 +335,6 @@
             val providerInfo by collectLastValue(underTest.appWidgetProviders)
 
             start()
-            runCurrent()
 
             // Assert that the provider info map is populated
             assertThat(providerInfo)
@@ -360,7 +345,6 @@
             whenever(appWidgetManager.getAppWidgetInfo(3)).thenReturn(providerInfo3)
             val newWidgetComponentName = ComponentName.unflattenFromString("pkg_new/cls_new")!!
             underTest.allocateIdAndBindWidget(newWidgetComponentName)
-            runCurrent()
 
             // Assert that the new provider is reflected in the flow
             assertThat(providerInfo)
@@ -371,7 +355,7 @@
 
     @Test
     fun providerInfo_updateWhenWidgetRemoved() =
-        testScope.runTest {
+        kosmos.runTest {
             whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2))
             whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1)
             whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2)
@@ -379,7 +363,6 @@
             val providerInfo by collectLastValue(underTest.appWidgetProviders)
 
             val observer = start()
-            runCurrent()
 
             // Assert that the provider info map is populated
             assertThat(providerInfo)
@@ -387,7 +370,6 @@
 
             // Remove widget 1
             observer.onDeleteAppWidgetId(1)
-            runCurrent()
 
             // Assert that provider info for widget 1 is removed
             assertThat(providerInfo).containsExactlyEntriesIn(mapOf(Pair(2, providerInfo2)))
@@ -401,9 +383,8 @@
             )
     }
 
-    private fun TestScope.start(): CommunalAppWidgetHost.Observer {
+    private fun start(): CommunalAppWidgetHost.Observer {
         underTest.startObservingHost()
-        runCurrent()
 
         val observer =
             withArgCaptor<CommunalAppWidgetHost.Observer> {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt
index 789b10b..ac06a3b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt
@@ -24,21 +24,19 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.communalSceneRepository
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2Available
 import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
 import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.flags.parameterizeSceneContainerFlag
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -47,14 +45,11 @@
 import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
-@EnableFlags(Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON, Flags.FLAG_GLANCEABLE_HUB_V2)
+@EnableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
 @RunWith(ParameterizedAndroidJunit4::class)
 class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : SysuiTestCase() {
     private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-
-    private lateinit var underTest: GlanceableHubQuickAffordanceConfig
+    private val Kosmos.underTest by Kosmos.Fixture { glanceableHubQuickAffordanceConfig }
 
     init {
         mSetFlagsRule.setFlagsParameterization(flags!!)
@@ -64,20 +59,16 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        underTest =
-            GlanceableHubQuickAffordanceConfig(
-                context = context,
-                communalInteractor = kosmos.communalInteractor,
-                communalSceneRepository = kosmos.communalSceneRepository,
-                communalSettingsInteractor = kosmos.communalSettingsInteractor,
-                sceneInteractor = kosmos.sceneInteractor,
-            )
+        // Access the class immediately so that flows are instantiated.
+        // GlanceableHubQuickAffordanceConfig accesses StateFlow.value directly so we need the flows
+        // to start flowing before runCurrent is called in the tests.
+        kosmos.underTest
     }
 
     @Test
     fun lockscreenState_whenGlanceableHubEnabled_returnsVisible() =
-        testScope.runTest {
-            kosmos.setCommunalV2Enabled(true)
+        kosmos.runTest {
+            kosmos.setCommunalV2Available(true)
             runCurrent()
 
             val lockScreenState by collectLastValue(underTest.lockScreenState)
@@ -88,8 +79,21 @@
 
     @Test
     fun lockscreenState_whenGlanceableHubDisabled_returnsHidden() =
-        testScope.runTest {
-            kosmos.setCommunalV2Enabled(false)
+        kosmos.runTest {
+            setCommunalV2Enabled(false)
+            val lockScreenState by collectLastValue(underTest.lockScreenState)
+            runCurrent()
+
+            assertThat(lockScreenState)
+                .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+        }
+
+    @Test
+    fun lockscreenState_whenGlanceableHubNotAvailable_returnsHidden() =
+        kosmos.runTest {
+            // Hub is enabled, but not available.
+            setCommunalV2Enabled(true)
+            fakeKeyguardRepository.setKeyguardShowing(false)
             val lockScreenState by collectLastValue(underTest.lockScreenState)
             runCurrent()
 
@@ -99,8 +103,8 @@
 
     @Test
     fun pickerScreenState_whenGlanceableHubEnabled_returnsDefault() =
-        testScope.runTest {
-            kosmos.setCommunalV2Enabled(true)
+        kosmos.runTest {
+            setCommunalV2Enabled(true)
             runCurrent()
 
             assertThat(underTest.getPickerScreenState())
@@ -109,8 +113,8 @@
 
     @Test
     fun pickerScreenState_whenGlanceableHubDisabled_returnsDisabled() =
-        testScope.runTest {
-            kosmos.setCommunalV2Enabled(false)
+        kosmos.runTest {
+            setCommunalV2Enabled(false)
             runCurrent()
 
             assertThat(
@@ -122,7 +126,7 @@
     @Test
     @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
     fun onTriggered_changesSceneToCommunal() =
-        testScope.runTest {
+        kosmos.runTest {
             underTest.onTriggered(expandable = null)
             runCurrent()
 
@@ -133,7 +137,7 @@
     @Test
     @EnableFlags(Flags.FLAG_SCENE_CONTAINER)
     fun testTransitionToGlanceableHub_sceneContainer() =
-        testScope.runTest {
+        kosmos.runTest {
             underTest.onTriggered(expandable = null)
             runCurrent()
 
@@ -145,11 +149,7 @@
         @JvmStatic
         @Parameters(name = "{0}")
         fun getParams(): List<FlagsParameterization> {
-            return FlagsParameterization.allCombinationsOf(
-                    Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON,
-                    Flags.FLAG_GLANCEABLE_HUB_V2,
-                )
-                .andSceneContainer()
+            return parameterizeSceneContainerFlag()
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 4a422f0..8c54ca1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
@@ -84,6 +85,7 @@
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = FakeUserTracker(),
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
         settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index 0f3e78b..bc2c2d2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -20,13 +20,17 @@
 import android.content.Intent
 import android.content.SharedPreferences
 import android.content.pm.UserInfo
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.backup.BackupHelper
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.res.R
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.UserFileManager
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -54,6 +58,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     @Mock private lateinit var userFileManager: UserFileManager
 
@@ -80,6 +85,7 @@
                 context = context,
                 userFileManager = userFileManager,
                 userTracker = userTracker,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
     }
@@ -110,27 +116,13 @@
         val affordanceId2 = "affordance2"
         val affordanceId3 = "affordance3"
 
-        underTest.setSelections(
-            slotId = slotId1,
-            affordanceIds = listOf(affordanceId1),
-        )
-        assertSelections(
-            affordanceIdsBySlotId.last(),
-            mapOf(
-                slotId1 to listOf(affordanceId1),
-            ),
-        )
+        underTest.setSelections(slotId = slotId1, affordanceIds = listOf(affordanceId1))
+        assertSelections(affordanceIdsBySlotId.last(), mapOf(slotId1 to listOf(affordanceId1)))
 
-        underTest.setSelections(
-            slotId = slotId2,
-            affordanceIds = listOf(affordanceId2),
-        )
+        underTest.setSelections(slotId = slotId2, affordanceIds = listOf(affordanceId2))
         assertSelections(
             affordanceIdsBySlotId.last(),
-            mapOf(
-                slotId1 to listOf(affordanceId1),
-                slotId2 to listOf(affordanceId2),
-            )
+            mapOf(slotId1 to listOf(affordanceId1), slotId2 to listOf(affordanceId2)),
         )
 
         underTest.setSelections(
@@ -139,34 +131,19 @@
         )
         assertSelections(
             affordanceIdsBySlotId.last(),
-            mapOf(
-                slotId1 to listOf(affordanceId1, affordanceId3),
-                slotId2 to listOf(affordanceId2),
-            )
+            mapOf(slotId1 to listOf(affordanceId1, affordanceId3), slotId2 to listOf(affordanceId2)),
         )
 
-        underTest.setSelections(
-            slotId = slotId1,
-            affordanceIds = listOf(affordanceId3),
-        )
+        underTest.setSelections(slotId = slotId1, affordanceIds = listOf(affordanceId3))
         assertSelections(
             affordanceIdsBySlotId.last(),
-            mapOf(
-                slotId1 to listOf(affordanceId3),
-                slotId2 to listOf(affordanceId2),
-            )
+            mapOf(slotId1 to listOf(affordanceId3), slotId2 to listOf(affordanceId2)),
         )
 
-        underTest.setSelections(
-            slotId = slotId2,
-            affordanceIds = listOf(),
-        )
+        underTest.setSelections(slotId = slotId2, affordanceIds = listOf())
         assertSelections(
             affordanceIdsBySlotId.last(),
-            mapOf(
-                slotId1 to listOf(affordanceId3),
-                slotId2 to listOf(),
-            )
+            mapOf(slotId1 to listOf(affordanceId3), slotId2 to listOf()),
         )
 
         job.cancel()
@@ -174,10 +151,7 @@
 
     @Test
     fun remembersSelectionsByUser() = runTest {
-        overrideResource(
-            R.array.config_keyguardQuickAffordanceDefaults,
-            arrayOf<String>(),
-        )
+        overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
         val slot1 = "slot_1"
         val slot2 = "slot_2"
         val affordance1 = "affordance_1"
@@ -195,60 +169,28 @@
                 UserInfo(/* id= */ 0, "zero", /* flags= */ 0),
                 UserInfo(/* id= */ 1, "one", /* flags= */ 0),
             )
-        userTracker.set(
-            userInfos = userInfos,
-            selectedUserIndex = 0,
-        )
-        underTest.setSelections(
-            slotId = slot1,
-            affordanceIds = listOf(affordance1),
-        )
-        underTest.setSelections(
-            slotId = slot2,
-            affordanceIds = listOf(affordance2),
-        )
+        userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
+        underTest.setSelections(slotId = slot1, affordanceIds = listOf(affordance1))
+        underTest.setSelections(slotId = slot2, affordanceIds = listOf(affordance2))
 
         // Switch to user 1
-        userTracker.set(
-            userInfos = userInfos,
-            selectedUserIndex = 1,
-        )
+        userTracker.set(userInfos = userInfos, selectedUserIndex = 1)
         // We never set selections on user 1, so it should be empty.
-        assertSelections(
-            observed = affordanceIdsBySlotId.last(),
-            expected = emptyMap(),
-        )
+        assertSelections(observed = affordanceIdsBySlotId.last(), expected = emptyMap())
         // Now, let's set selections on user 1.
-        underTest.setSelections(
-            slotId = slot1,
-            affordanceIds = listOf(affordance2),
-        )
-        underTest.setSelections(
-            slotId = slot2,
-            affordanceIds = listOf(affordance3),
-        )
+        underTest.setSelections(slotId = slot1, affordanceIds = listOf(affordance2))
+        underTest.setSelections(slotId = slot2, affordanceIds = listOf(affordance3))
         assertSelections(
             observed = affordanceIdsBySlotId.last(),
-            expected =
-                mapOf(
-                    slot1 to listOf(affordance2),
-                    slot2 to listOf(affordance3),
-                ),
+            expected = mapOf(slot1 to listOf(affordance2), slot2 to listOf(affordance3)),
         )
 
         // Switch back to user 0.
-        userTracker.set(
-            userInfos = userInfos,
-            selectedUserIndex = 0,
-        )
+        userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
         // Assert that we still remember the old selections for user 0.
         assertSelections(
             observed = affordanceIdsBySlotId.last(),
-            expected =
-                mapOf(
-                    slot1 to listOf(affordance1),
-                    slot2 to listOf(affordance2),
-                ),
+            expected = mapOf(slot1 to listOf(affordance1), slot2 to listOf(affordance2)),
         )
 
         job.cancel()
@@ -276,10 +218,7 @@
 
         assertSelections(
             affordanceIdsBySlotId.last(),
-            mapOf(
-                slotId1 to listOf(affordanceId1, affordanceId3),
-                slotId2 to listOf(affordanceId2),
-            ),
+            mapOf(slotId1 to listOf(affordanceId1, affordanceId3), slotId2 to listOf(affordanceId2)),
         )
 
         job.cancel()
@@ -308,10 +247,7 @@
         underTest.setSelections(slotId1, listOf(affordanceId2))
         assertSelections(
             affordanceIdsBySlotId.last(),
-            mapOf(
-                slotId1 to listOf(affordanceId2),
-                slotId2 to listOf(affordanceId2),
-            ),
+            mapOf(slotId1 to listOf(affordanceId2), slotId2 to listOf(affordanceId2)),
         )
 
         job.cancel()
@@ -340,10 +276,7 @@
         underTest.setSelections(slotId1, listOf())
         assertSelections(
             affordanceIdsBySlotId.last(),
-            mapOf(
-                slotId1 to listOf(),
-                slotId2 to listOf(affordanceId2),
-            ),
+            mapOf(slotId1 to listOf(), slotId2 to listOf(affordanceId2)),
         )
 
         job.cancel()
@@ -373,18 +306,36 @@
         overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, false)
         overrideResource(
             R.array.config_keyguardQuickAffordanceDefaults,
-            arrayOf("leftTest:testShortcut1", "rightTest:testShortcut2")
+            arrayOf("leftTest:testShortcut1", "rightTest:testShortcut2"),
         )
 
         assertThat(underTest.getSelections())
             .isEqualTo(
-                mapOf(
-                    "leftTest" to listOf("testShortcut1"),
-                    "rightTest" to listOf("testShortcut2"),
-                )
+                mapOf("leftTest" to listOf("testShortcut1"), "rightTest" to listOf("testShortcut2"))
             )
     }
 
+    @EnableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
+    @Test
+    fun getSelections_returnsSelectionsIfHubV2Enabled() = runTest {
+        overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, false)
+        overrideResource(com.android.internal.R.bool.config_glanceableHubEnabled, true)
+
+        overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
+        val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+        val job =
+            launch(UnconfinedTestDispatcher()) {
+                underTest.selections.toList(affordanceIdsBySlotId)
+            }
+        val slotId1 = "slot1"
+        val affordanceId1 = "affordance1"
+
+        underTest.setSelections(slotId = slotId1, affordanceIds = listOf(affordanceId1))
+        assertSelections(affordanceIdsBySlotId.last(), mapOf(slotId1 to listOf(affordanceId1)))
+
+        job.cancel()
+    }
+
     private fun assertSelections(
         observed: Map<String, List<String>>?,
         expected: Map<String, List<String>>,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 1582e47..a101818 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
@@ -84,16 +85,11 @@
                 context = context,
                 userFileManager =
                     mock<UserFileManager>().apply {
-                        whenever(
-                                getSharedPreferences(
-                                    anyString(),
-                                    anyInt(),
-                                    anyInt(),
-                                )
-                            )
+                        whenever(getSharedPreferences(anyString(), anyInt(), anyInt()))
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = userTracker,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
         client1 = FakeCustomizationProviderClient()
@@ -103,9 +99,8 @@
                 scope = testScope.backgroundScope,
                 userTracker = userTracker,
                 clientFactory =
-                    FakeKeyguardQuickAffordanceProviderClientFactory(
-                        userTracker,
-                    ) { selectedUserId ->
+                    FakeKeyguardQuickAffordanceProviderClientFactory(userTracker) { selectedUserId
+                        ->
                         when (selectedUserId) {
                             SECONDARY_USER_1 -> client1
                             SECONDARY_USER_2 -> client2
@@ -115,10 +110,7 @@
                 userHandle = UserHandle.SYSTEM,
             )
 
-        overrideResource(
-            R.array.config_keyguardQuickAffordanceDefaults,
-            arrayOf<String>(),
-        )
+        overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
 
         underTest =
             KeyguardQuickAffordanceRepository(
@@ -155,30 +147,19 @@
             val slotId2 = "slot2"
 
             underTest.setSelections(slotId1, listOf(config1.key))
-            assertSelections(
-                configsBySlotId(),
-                mapOf(
-                    slotId1 to listOf(config1),
-                ),
-            )
+            assertSelections(configsBySlotId(), mapOf(slotId1 to listOf(config1)))
 
             underTest.setSelections(slotId2, listOf(config2.key))
             assertSelections(
                 configsBySlotId(),
-                mapOf(
-                    slotId1 to listOf(config1),
-                    slotId2 to listOf(config2),
-                ),
+                mapOf(slotId1 to listOf(config1), slotId2 to listOf(config2)),
             )
 
             underTest.setSelections(slotId1, emptyList())
             underTest.setSelections(slotId2, listOf(config1.key))
             assertSelections(
                 configsBySlotId(),
-                mapOf(
-                    slotId1 to emptyList(),
-                    slotId2 to listOf(config1),
-                ),
+                mapOf(slotId1 to emptyList(), slotId2 to listOf(config1)),
             )
         }
 
@@ -209,28 +190,15 @@
         val slot3 = "slot3"
         context.orCreateTestableResources.addOverride(
             R.array.config_keyguardQuickAffordanceSlots,
-            arrayOf(
-                "$slot1:2",
-                "$slot2:4",
-                "$slot3:5",
-            ),
+            arrayOf("$slot1:2", "$slot2:4", "$slot3:5"),
         )
 
         assertThat(underTest.getSlotPickerRepresentations())
             .isEqualTo(
                 listOf(
-                    KeyguardSlotPickerRepresentation(
-                        id = slot1,
-                        maxSelectedAffordances = 2,
-                    ),
-                    KeyguardSlotPickerRepresentation(
-                        id = slot2,
-                        maxSelectedAffordances = 4,
-                    ),
-                    KeyguardSlotPickerRepresentation(
-                        id = slot3,
-                        maxSelectedAffordances = 5,
-                    ),
+                    KeyguardSlotPickerRepresentation(id = slot1, maxSelectedAffordances = 2),
+                    KeyguardSlotPickerRepresentation(id = slot2, maxSelectedAffordances = 4),
+                    KeyguardSlotPickerRepresentation(id = slot3, maxSelectedAffordances = 5),
                 )
             )
     }
@@ -243,28 +211,15 @@
         val slot3 = "slot3"
         context.orCreateTestableResources.addOverride(
             R.array.config_keyguardQuickAffordanceSlots,
-            arrayOf(
-                "$slot1:2",
-                "$slot2:4",
-                "$slot3:5",
-            ),
+            arrayOf("$slot1:2", "$slot2:4", "$slot3:5"),
         )
 
         assertThat(underTest.getSlotPickerRepresentations())
             .isEqualTo(
                 listOf(
-                    KeyguardSlotPickerRepresentation(
-                        id = slot3,
-                        maxSelectedAffordances = 5,
-                    ),
-                    KeyguardSlotPickerRepresentation(
-                        id = slot2,
-                        maxSelectedAffordances = 4,
-                    ),
-                    KeyguardSlotPickerRepresentation(
-                        id = slot1,
-                        maxSelectedAffordances = 2,
-                    ),
+                    KeyguardSlotPickerRepresentation(id = slot3, maxSelectedAffordances = 5),
+                    KeyguardSlotPickerRepresentation(id = slot2, maxSelectedAffordances = 4),
+                    KeyguardSlotPickerRepresentation(id = slot1, maxSelectedAffordances = 2),
                 )
             )
     }
@@ -275,21 +230,9 @@
             userTracker.set(
                 userInfos =
                     listOf(
-                        UserInfo(
-                            UserHandle.USER_SYSTEM,
-                            "Primary",
-                            /* flags= */ 0,
-                        ),
-                        UserInfo(
-                            SECONDARY_USER_1,
-                            "Secondary 1",
-                            /* flags= */ 0,
-                        ),
-                        UserInfo(
-                            SECONDARY_USER_2,
-                            "Secondary 2",
-                            /* flags= */ 0,
-                        ),
+                        UserInfo(UserHandle.USER_SYSTEM, "Primary", /* flags= */ 0),
+                        UserInfo(SECONDARY_USER_1, "Secondary 1", /* flags= */ 0),
+                        UserInfo(SECONDARY_USER_2, "Secondary 2", /* flags= */ 0),
                     ),
                 selectedUserIndex = 2,
             )
@@ -302,12 +245,7 @@
             assertSelections(
                 observed = observed(),
                 expected =
-                    mapOf(
-                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
-                            listOf(
-                                config2,
-                            ),
-                    )
+                    mapOf(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to listOf(config2)),
             )
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 9de0215..fe9da0d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.dock.DockManager
@@ -147,6 +148,7 @@
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = userTracker,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
         val remoteUserSelectionManager =
@@ -196,6 +198,7 @@
                 biometricSettingsRepository = biometricSettingsRepository,
                 backgroundDispatcher = kosmos.testDispatcher,
                 appContext = context,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 sceneInteractor = { kosmos.sceneInteractor },
             )
         kosmos.keyguardQuickAffordanceInteractor = underTest
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
index ad5eeab..26fe379 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -118,6 +119,50 @@
             )
         }
 
+    /** STL: Ls -> overlay, then settle with Idle(overlay). */
+    @Test
+    fun transition_overlay_from_ls_scene_end_in_gone() =
+        testScope.runTest {
+            sceneTransitions.value =
+                ObservableTransitionState.Transition.ShowOrHideOverlay(
+                    overlay = Overlays.NotificationsShade,
+                    fromContent = Scenes.Lockscreen,
+                    toContent = Overlays.NotificationsShade,
+                    currentScene = Scenes.Lockscreen,
+                    currentOverlays = flowOf(emptySet()),
+                    progress,
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                    previewProgress = flowOf(0f),
+                    isInPreviewStage = flowOf(false),
+                )
+
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.RUNNING,
+                progress = 0f,
+            )
+
+            progress.value = 0.4f
+            assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
+
+            sceneTransitions.value =
+                ObservableTransitionState.Idle(
+                    Scenes.Lockscreen,
+                    setOf(Overlays.NotificationsShade),
+                )
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.UNDEFINED,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+        }
+
     /**
      * STL: Ls -> Gone, then settle with Idle(Ls). KTF in this scenario needs to invert the
      * transition LS -> UNDEFINED to UNDEFINED -> LS as there is no mechanism in KTF to
@@ -259,6 +304,47 @@
             )
         }
 
+    /** STL: Ls with overlay, then settle with Idle(Ls). */
+    @Test
+    fun transition_overlay_to_ls_scene_end_in_ls() =
+        testScope.runTest {
+            val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+            sceneTransitions.value =
+                ObservableTransitionState.Transition.ShowOrHideOverlay(
+                    overlay = Overlays.NotificationsShade,
+                    fromContent = Overlays.NotificationsShade,
+                    toContent = Scenes.Lockscreen,
+                    currentScene = Scenes.Lockscreen,
+                    currentOverlays = flowOf(setOf(Overlays.NotificationsShade)),
+                    progress,
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                    previewProgress = flowOf(0f),
+                    isInPreviewStage = flowOf(false),
+                )
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.RUNNING,
+                progress = 0f,
+            )
+
+            progress.value = 0.4f
+            assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
+
+            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+
+            assertTransition(
+                step = currentStep!!,
+                from = KeyguardState.UNDEFINED,
+                to = KeyguardState.LOCKSCREEN,
+                state = TransitionState.FINISHED,
+                progress = 1f,
+            )
+        }
+
     /** STL: Gone -> Ls (AOD), will transition to AOD once */
     @Test
     fun transition_to_ls_scene_with_changed_next_scene_is_respected_just_once() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
index 056efb3..c47a412 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
@@ -117,7 +117,6 @@
                     "test_spec:\n" +
                         "    QSTileState(" +
                         "icon=Resource(res=0, contentDescription=Resource(res=0)), " +
-                        "iconRes=null, " +
                         "label=test_data, " +
                         "activationState=INACTIVE, " +
                         "secondaryLabel=null, " +
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
index 00460bf..557f4ea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
@@ -93,8 +93,7 @@
     ): QSTileState {
         val label = context.getString(R.string.airplane_mode)
         return QSTileState(
-            Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes,
+            Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
index 632aae0..24e4668 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -178,8 +178,7 @@
     ): QSTileState {
         val label = context.getString(R.string.status_bar_alarm)
         return QSTileState(
-            Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null),
-            R.drawable.ic_alarm,
+            Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null, R.drawable.ic_alarm),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
index 5385f94..2ddaddd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -253,8 +253,7 @@
     ): QSTileState {
         val label = context.getString(R.string.battery_detail_switch_title)
         return QSTileState(
-            Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes,
+            Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
index 356b98e..a3c159820 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
@@ -77,8 +77,11 @@
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_color_correction_label)
         return QSTileState(
-            Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null),
-            R.drawable.ic_qs_color_correction,
+            Icon.Loaded(
+                context.getDrawable(R.drawable.ic_qs_color_correction)!!,
+                null,
+                R.drawable.ic_qs_color_correction,
+            ),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
index 8236c4c..608adf1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -253,7 +253,6 @@
     ): QSTileState {
         return QSTileState(
             icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) },
-            null,
             "test label",
             activationState,
             "test subtitle",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index 587585c..a115c12 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -73,7 +73,11 @@
             mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true))
 
         val expectedIcon =
-            Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
+            Icon.Loaded(
+                context.getDrawable(R.drawable.qs_flashlight_icon_on)!!,
+                null,
+                R.drawable.qs_flashlight_icon_on,
+            )
         val actualIcon = tileState.icon
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
@@ -84,7 +88,11 @@
             mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false))
 
         val expectedIcon =
-            Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
+            Icon.Loaded(
+                context.getDrawable(R.drawable.qs_flashlight_icon_off)!!,
+                null,
+                R.drawable.qs_flashlight_icon_off,
+            )
         val actualIcon = tileState.icon
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
@@ -95,7 +103,11 @@
             mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable)
 
         val expectedIcon =
-            Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
+            Icon.Loaded(
+                context.getDrawable(R.drawable.qs_flashlight_icon_off)!!,
+                null,
+                R.drawable.qs_flashlight_icon_off,
+            )
         val actualIcon = tileState.icon
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
index e81771e..8f8f710 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -58,8 +58,11 @@
 
     private fun createFontScalingTileState(): QSTileState =
         QSTileState(
-            Icon.Loaded(context.getDrawable(R.drawable.ic_qs_font_scaling)!!, null),
-            R.drawable.ic_qs_font_scaling,
+            Icon.Loaded(
+                context.getDrawable(R.drawable.ic_qs_font_scaling)!!,
+                null,
+                R.drawable.ic_qs_font_scaling,
+            ),
             context.getString(R.string.quick_settings_font_scaling_label),
             QSTileState.ActivationState.ACTIVE,
             null,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
index 12d604f..3d3447d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
@@ -102,8 +102,7 @@
         val label = context.getString(R.string.quick_settings_hearing_devices_label)
         val iconRes = R.drawable.qs_hearing_devices_icon
         return QSTileState(
-            Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes,
+            Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
index 9dcf49e..b087bbc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -82,7 +82,6 @@
                 QSTileState.ActivationState.ACTIVE,
                 context.getString(R.string.quick_settings_networks_available),
                 Icon.Loaded(signalDrawable, null),
-                null,
                 context.getString(R.string.quick_settings_internet_label),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
@@ -120,8 +119,11 @@
             createInternetTileState(
                 QSTileState.ActivationState.ACTIVE,
                 inputModel.secondaryLabel.loadText(context).toString(),
-                Icon.Loaded(context.getDrawable(expectedSatIcon!!.res)!!, null),
-                expectedSatIcon.res,
+                Icon.Loaded(
+                    context.getDrawable(expectedSatIcon!!.res)!!,
+                    null,
+                    expectedSatIcon.res,
+                ),
                 expectedSatIcon.contentDescription.loadContentDescription(context).toString(),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
@@ -144,8 +146,7 @@
             createInternetTileState(
                 QSTileState.ActivationState.ACTIVE,
                 context.getString(R.string.quick_settings_networks_available),
-                Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null),
-                wifiRes,
+                Icon.Loaded(context.getDrawable(wifiRes)!!, null, wifiRes),
                 context.getString(R.string.quick_settings_internet_label),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
@@ -171,8 +172,8 @@
                 Icon.Loaded(
                     context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!,
                     contentDescription = null,
+                    R.drawable.ic_qs_no_internet_unavailable,
                 ),
-                R.drawable.ic_qs_no_internet_unavailable,
                 context.getString(R.string.quick_settings_networks_unavailable),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
@@ -182,13 +183,11 @@
         activationState: QSTileState.ActivationState,
         secondaryLabel: String,
         icon: Icon,
-        iconRes: Int? = null,
         contentDescription: String,
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_internet_label)
         return QSTileState(
             icon,
-            iconRes,
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
index 30fce73..780d58c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
@@ -90,8 +90,7 @@
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_inversion_label)
         return QSTileState(
-            Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes,
+            Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
index 37e8a60..4ebe23dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
@@ -69,7 +69,12 @@
     fun mapsEnabledDataToOnIconState() {
         val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))
 
-        val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_on)!!, null)
+        val expectedIcon =
+            Icon.Loaded(
+                context.getDrawable(R.drawable.qs_location_icon_on)!!,
+                null,
+                R.drawable.qs_location_icon_on,
+            )
         val actualIcon = tileState.icon
         Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
     }
@@ -78,7 +83,12 @@
     fun mapsDisabledDataToOffIconState() {
         val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))
 
-        val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_off)!!, null)
+        val expectedIcon =
+            Icon.Loaded(
+                context.getDrawable(R.drawable.qs_location_icon_off)!!,
+                null,
+                R.drawable.qs_location_icon_off,
+            )
         val actualIcon = tileState.icon
         Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index d16342b..44e6b4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -146,13 +146,13 @@
             // Tile starts with the generic Modes icon.
             runCurrent()
             assertThat(tileData?.icon).isEqualTo(MODES_ICON)
-            assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+            assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID)
 
             // Add an inactive mode -> Still modes icon
             zenModeRepository.addMode(id = "Mode", active = false)
             runCurrent()
             assertThat(tileData?.icon).isEqualTo(MODES_ICON)
-            assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+            assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID)
 
             // Add an active mode with a default icon: icon should be the mode icon, and the
             // iconResId is also populated, because we know it's a system icon.
@@ -163,7 +163,7 @@
             )
             runCurrent()
             assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON)
-            assertThat(tileData?.iconResId).isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
+            assertThat(tileData?.icon!!.res).isEqualTo(BEDTIME_DRAWABLE_ID)
 
             // Add another, less-prioritized mode that has a *custom* icon: for now, icon should
             // remain the first mode icon
@@ -178,20 +178,20 @@
             )
             runCurrent()
             assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON)
-            assertThat(tileData?.iconResId).isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
+            assertThat(tileData?.icon!!.res).isEqualTo(BEDTIME_DRAWABLE_ID)
 
             // Deactivate more important mode: icon should be the less important, still active mode
             // And because it's a package-provided icon, iconResId is not populated.
             zenModeRepository.deactivateMode("Bedtime with default icon")
             runCurrent()
             assertThat(tileData?.icon).isEqualTo(CUSTOM_ICON)
-            assertThat(tileData?.iconResId).isNull()
+            assertThat(tileData?.icon!!.res).isNull()
 
             // Deactivate remaining mode: back to the default modes icon
             zenModeRepository.deactivateMode("Driving with custom icon")
             runCurrent()
             assertThat(tileData?.icon).isEqualTo(MODES_ICON)
-            assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+            assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID)
         }
 
     @Test
@@ -206,18 +206,18 @@
 
             runCurrent()
             assertThat(tileData?.icon).isEqualTo(MODES_ICON)
-            assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+            assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID)
 
             // Activate a Mode -> Icon doesn't change.
             zenModeRepository.addMode(id = "Mode", active = true)
             runCurrent()
             assertThat(tileData?.icon).isEqualTo(MODES_ICON)
-            assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+            assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID)
 
             zenModeRepository.deactivateMode(id = "Mode")
             runCurrent()
             assertThat(tileData?.icon).isEqualTo(MODES_ICON)
-            assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+            assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID)
         }
 
     @EnableFlags(Flags.FLAG_MODES_UI)
@@ -257,10 +257,10 @@
         val TEST_USER = UserHandle.of(1)!!
         const val CUSTOM_PACKAGE = "com.some.mode.owner.package"
 
-        val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes
+        const val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes
         const val CUSTOM_DRAWABLE_ID = 12345
 
-        val BEDTIME_DRAWABLE_ID = R.drawable.ic_zen_mode_type_bedtime
+        const val BEDTIME_DRAWABLE_ID = R.drawable.ic_zen_mode_type_bedtime
 
         val MODES_DRAWABLE = TestStubDrawable("modes_icon")
         val BEDTIME_DRAWABLE = TestStubDrawable("bedtime")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index 88b0046..04e094f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -156,6 +156,10 @@
     }
 
     private fun modelOf(isActivated: Boolean, activeModeNames: List<String>): ModesTileModel {
-        return ModesTileModel(isActivated, activeModeNames, TestStubDrawable("icon").asIcon(), 123)
+        return ModesTileModel(
+            isActivated,
+            activeModeNames,
+            TestStubDrawable("icon").asIcon(res = 123),
+        )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
index 4e91d16..d73044f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
@@ -99,18 +99,11 @@
 
     @Test
     fun state_modelHasIconResId_includesIconResId() {
-        val icon = TestStubDrawable("res123").asIcon()
-        val model =
-            ModesTileModel(
-                isActivated = false,
-                activeModes = emptyList(),
-                icon = icon,
-                iconResId = 123,
-            )
+        val icon = TestStubDrawable("res123").asIcon(res = 123)
+        val model = ModesTileModel(isActivated = false, activeModes = emptyList(), icon = icon)
 
         val state = underTest.map(config, model)
 
         assertThat(state.icon).isEqualTo(icon)
-        assertThat(state.iconRes).isEqualTo(123)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
index 1457f53..7c85326 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
@@ -289,8 +289,7 @@
             if (TextUtils.isEmpty(secondaryLabel)) label
             else TextUtils.concat(label, ", ", secondaryLabel)
         return QSTileState(
-            Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes,
+            Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt
index 2ac3e08..b6caa22 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt
@@ -58,8 +58,11 @@
 
     private fun createNotesTileState(): QSTileState =
         QSTileState(
-            Icon.Loaded(context.getDrawable(R.drawable.ic_qs_notes)!!, null),
-            R.drawable.ic_qs_notes,
+            Icon.Loaded(
+                context.getDrawable(R.drawable.ic_qs_notes)!!,
+                null,
+                R.drawable.ic_qs_notes,
+            ),
             context.getString(R.string.quick_settings_notes_label),
             QSTileState.ActivationState.INACTIVE,
             /* secondaryLabel= */ null,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
index 7782d2b..5b39810 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
@@ -66,11 +66,7 @@
         val outputState = mapper.map(config, inputModel)
 
         val expectedState =
-            createOneHandedModeTileState(
-                QSTileState.ActivationState.INACTIVE,
-                subtitleArray[1],
-                com.android.internal.R.drawable.ic_qs_one_handed_mode,
-            )
+            createOneHandedModeTileState(QSTileState.ActivationState.INACTIVE, subtitleArray[1])
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
 
@@ -81,23 +77,21 @@
         val outputState = mapper.map(config, inputModel)
 
         val expectedState =
-            createOneHandedModeTileState(
-                QSTileState.ActivationState.ACTIVE,
-                subtitleArray[2],
-                com.android.internal.R.drawable.ic_qs_one_handed_mode,
-            )
+            createOneHandedModeTileState(QSTileState.ActivationState.ACTIVE, subtitleArray[2])
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
 
     private fun createOneHandedModeTileState(
         activationState: QSTileState.ActivationState,
         secondaryLabel: String,
-        iconRes: Int,
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_onehanded_label)
         return QSTileState(
-            Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes,
+            Icon.Loaded(
+                context.getDrawable(com.android.internal.R.drawable.ic_qs_one_handed_mode)!!,
+                null,
+                com.android.internal.R.drawable.ic_qs_one_handed_mode,
+            ),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
index ed33250..c572ff6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -93,8 +93,8 @@
             Icon.Loaded(
                 context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
                 null,
+                com.android.systemui.res.R.drawable.ic_qr_code_scanner,
             ),
-            com.android.systemui.res.R.drawable.ic_qr_code_scanner,
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
index 85111fd..00017f9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
@@ -85,8 +85,7 @@
                 R.drawable.qs_extra_dim_icon_on
             else R.drawable.qs_extra_dim_icon_off
         return QSTileState(
-            Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes,
+            Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
             label,
             activationState,
             context.resources
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
index 53671ba..7401014 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
@@ -180,8 +180,7 @@
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_rotation_unlocked_label)
         return QSTileState(
-            Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes,
+            Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
index 9a45065..1fb5048 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
@@ -91,8 +91,7 @@
             else context.resources.getStringArray(R.array.tile_states_saver)[0]
 
         return QSTileState(
-            Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes,
+            Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
index cd683c4..3632556 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
@@ -110,8 +110,7 @@
         val label = context.getString(R.string.quick_settings_screen_record_label)
 
         return QSTileState(
-            Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes,
+            Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
index c569403..e4cd0e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
@@ -146,8 +146,7 @@
             else context.getString(R.string.quick_settings_mic_label)
 
         return QSTileState(
-            Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes,
+            Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
index 0d2ebe4..8f5f2d3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
@@ -69,8 +69,7 @@
         expandedAccessibilityClass: KClass<out View>? = Switch::class,
     ): QSTileState {
         return QSTileState(
-            Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes,
+            Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
index 86321ea..2c81f39 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
@@ -109,8 +109,7 @@
         val label = testLabel
         val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
         return QSTileState(
-            icon = Icon.Loaded(context.getDrawable(iconRes)!!, null),
-            iconRes = iconRes,
+            icon = Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
             label = label,
             activationState = activationState,
             secondaryLabel =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index d3b5828..07a408b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -45,7 +45,6 @@
 import android.content.ContentResolver;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
@@ -57,7 +56,6 @@
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.ViewPropertyAnimator;
-import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityManager;
 
@@ -74,10 +72,8 @@
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardStatusViewController;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
 import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
-import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
@@ -147,12 +143,13 @@
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
 import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository;
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -172,17 +169,13 @@
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.TapAgainViewController;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
@@ -227,8 +220,6 @@
     @Mock protected HeadsUpManager mHeadsUpManager;
     @Mock protected NotificationGutsManager mGutsManager;
     @Mock protected KeyguardStatusBarView mKeyguardStatusBar;
-    @Mock protected KeyguardUserSwitcherView mUserSwitcherView;
-    @Mock protected ViewStub mUserSwitcherStubView;
     @Mock protected HeadsUpTouchHelper.Callback mHeadsUpCallback;
     @Mock protected KeyguardUpdateMonitor mUpdateMonitor;
     @Mock protected KeyguardBypassController mKeyguardBypassController;
@@ -254,12 +245,6 @@
     @Mock protected ConversationNotificationManager mConversationNotificationManager;
     @Mock protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock protected KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-    @Mock protected KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
-    @Mock protected KeyguardQsUserSwitchComponent mKeyguardQsUserSwitchComponent;
-    @Mock protected KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
-    @Mock protected KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
-    @Mock protected KeyguardUserSwitcherComponent mKeyguardUserSwitcherComponent;
-    @Mock protected KeyguardUserSwitcherController mKeyguardUserSwitcherController;
     @Mock protected KeyguardStatusViewComponent mKeyguardStatusViewComponent;
     @Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
     @Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
@@ -477,9 +462,6 @@
                 .thenReturn(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
         when(mView.getContext()).thenReturn(getContext());
         when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
-        when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(mUserSwitcherView);
-        when(mView.findViewById(R.id.keyguard_user_switcher_stub)).thenReturn(
-                mUserSwitcherStubView);
         when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch);
         when(mView.findViewById(R.id.notification_stack_scroller))
                 .thenReturn(mNotificationStackScrollLayout);
@@ -511,14 +493,6 @@
         when(mFragmentService.getFragmentHostManager(mView)).thenReturn(mFragmentHostManager);
         FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
                 mDisplayMetrics);
-        when(mKeyguardQsUserSwitchComponentFactory.build(any()))
-                .thenReturn(mKeyguardQsUserSwitchComponent);
-        when(mKeyguardQsUserSwitchComponent.getKeyguardQsUserSwitchController())
-                .thenReturn(mKeyguardQsUserSwitchController);
-        when(mKeyguardUserSwitcherComponentFactory.build(any()))
-                .thenReturn(mKeyguardUserSwitcherComponent);
-        when(mKeyguardUserSwitcherComponent.getKeyguardUserSwitcherController())
-                .thenReturn(mKeyguardUserSwitcherController);
         when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true);
         when(mQs.getView()).thenReturn(mView);
         when(mQSFragment.getView()).thenReturn(mView);
@@ -627,8 +601,6 @@
                 .thenReturn(mKeyguardStatusBarViewController);
         when(mLayoutInflater.inflate(eq(R.layout.keyguard_status_view), any(), anyBoolean()))
                 .thenReturn(keyguardStatusView);
-        when(mLayoutInflater.inflate(eq(R.layout.keyguard_user_switcher), any(), anyBoolean()))
-                .thenReturn(mUserSwitcherView);
         when(mNotificationRemoteInputManager.isRemoteInputActive())
                 .thenReturn(false);
         doAnswer(invocation -> {
@@ -690,8 +662,6 @@
                 mNotificationsQSContainerController,
                 mNotificationStackScrollLayoutController,
                 mKeyguardStatusViewComponentFactory,
-                mKeyguardQsUserSwitchComponentFactory,
-                mKeyguardUserSwitcherComponentFactory,
                 mKeyguardStatusBarViewComponentFactory,
                 mLockscreenShadeTransitionController,
                 mAuthController,
@@ -883,16 +853,6 @@
         mNotificationPanelViewController.updateResources();
     }
 
-    protected void updateMultiUserSetting(boolean enabled) {
-        when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false);
-        when(mUserManager.isUserSwitcherEnabled(false)).thenReturn(enabled);
-        final ArgumentCaptor<ContentObserver> observerCaptor =
-                ArgumentCaptor.forClass(ContentObserver.class);
-        verify(mContentResolver)
-                .registerContentObserver(any(), anyBoolean(), observerCaptor.capture());
-        observerCaptor.getValue().onChange(/* selfChange */ false);
-    }
-
     protected void updateSmallestScreenWidth(int smallestScreenWidthDp) {
         Configuration configuration = new Configuration();
         configuration.smallestScreenWidthDp = smallestScreenWidthDp;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 550fcf7..51f00a0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -16,59 +16,28 @@
 
 package com.android.systemui.shade;
 
-import static com.android.keyguard.KeyguardClockSwitch.LARGE;
-import static com.android.keyguard.KeyguardClockSwitch.SMALL;
-import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
-import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
-import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
-import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.animation.Animator;
-import android.animation.ValueAnimator;
-import android.graphics.Point;
-import android.os.PowerManager;
-import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
 
-import androidx.constraintlayout.widget.ConstraintSet;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.DejankUtils;
 import com.android.systemui.flags.DisableSceneContainer;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.domain.interactor.PowerInteractor;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
-import com.android.systemui.statusbar.notification.stack.AmbientState;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
 
 import com.google.android.msdl.data.model.MSDLToken;
 
@@ -76,10 +45,6 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-
-import java.util.List;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -91,83 +56,6 @@
         DejankUtils.setImmediate(true);
     }
 
-    /**
-     * When the Back gesture starts (progress 0%), the scrim will stay at 100% scale (1.0f).
-     */
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testBackGesture_min_scrimAtMaxScale() {
-        mNotificationPanelViewController.onBackProgressed(0.0f);
-        verify(mScrimController).applyBackScaling(1.0f);
-    }
-
-    /**
-     * When the Back gesture is at max (progress 100%), the scrim will be scaled to its minimum.
-     */
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testBackGesture_max_scrimAtMinScale() {
-        mNotificationPanelViewController.onBackProgressed(1.0f);
-        verify(mScrimController).applyBackScaling(
-                NotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onNotificationHeightChangeWhileOnKeyguardWillComputeMaxKeyguardNotifications() {
-        mStatusBarStateController.setState(KEYGUARD);
-        ArgumentCaptor<OnHeightChangedListener> captor =
-                ArgumentCaptor.forClass(OnHeightChangedListener.class);
-        verify(mNotificationStackScrollLayoutController)
-                .setOnHeightChangedListener(captor.capture());
-        OnHeightChangedListener listener = captor.getValue();
-
-        clearInvocations(mNotificationStackSizeCalculator);
-        listener.onHeightChanged(mock(ExpandableView.class), false);
-
-        verify(mNotificationStackSizeCalculator)
-                .computeMaxKeyguardNotifications(any(), anyFloat(), anyFloat(), anyFloat());
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onNotificationHeightChangeWhileInShadeWillNotComputeMaxKeyguardNotifications() {
-        mStatusBarStateController.setState(SHADE);
-        ArgumentCaptor<OnHeightChangedListener> captor =
-                ArgumentCaptor.forClass(OnHeightChangedListener.class);
-        verify(mNotificationStackScrollLayoutController)
-                .setOnHeightChangedListener(captor.capture());
-        OnHeightChangedListener listener = captor.getValue();
-
-        clearInvocations(mNotificationStackSizeCalculator);
-        listener.onHeightChanged(mock(ExpandableView.class), false);
-
-        verify(mNotificationStackSizeCalculator, never())
-                .computeMaxKeyguardNotifications(any(), anyFloat(), anyFloat(), anyFloat());
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void computeMaxKeyguardNotifications_lockscreenToShade_returnsExistingMax() {
-        when(mAmbientState.getFractionToShade()).thenReturn(0.5f);
-        mNotificationPanelViewController.setMaxDisplayedNotifications(-1);
-
-        // computeMaxKeyguardNotifications sets maxAllowed to 0 at minimum if it updates the value
-        assertThat(mNotificationPanelViewController.computeMaxKeyguardNotifications())
-                .isEqualTo(-1);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void computeMaxKeyguardNotifications_noTransition_updatesMax() {
-        when(mAmbientState.getFractionToShade()).thenReturn(0f);
-        mNotificationPanelViewController.setMaxDisplayedNotifications(-1);
-
-        // computeMaxKeyguardNotifications sets maxAllowed to 0 at minimum if it updates the value
-        assertThat(mNotificationPanelViewController.computeMaxKeyguardNotifications())
-                .isNotEqualTo(-1);
-    }
-
     @Test
     @Ignore("b/261472011 - Test appears inconsistent across environments")
     public void getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable() {
@@ -205,165 +93,6 @@
     }
 
     @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void getVerticalSpaceForLockscreenShelf_useLockIconBottomPadding_returnsShelfHeight() {
-        enableSplitShade(/* enabled= */ false);
-        setBottomPadding(/* stackScrollLayoutBottom= */ 100,
-                /* lockIconPadding= */ 20,
-                /* indicationPadding= */ 0,
-                /* ambientPadding= */ 0);
-
-        when(mNotificationStackScrollLayoutController.getShelfHeight()).thenReturn(5);
-        assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
-                .isEqualTo(5);
-
-        assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
-                .isEqualTo(5);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void getVerticalSpaceForLockscreenShelf_useIndicationBottomPadding_returnsZero() {
-        enableSplitShade(/* enabled= */ false);
-        setBottomPadding(/* stackScrollLayoutBottom= */ 100,
-                /* lockIconPadding= */ 0,
-                /* indicationPadding= */ 30,
-                /* ambientPadding= */ 0);
-
-        when(mNotificationStackScrollLayoutController.getShelfHeight()).thenReturn(5);
-        assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
-                .isEqualTo(0);
-
-        assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
-                .isEqualTo(0);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void getVerticalSpaceForLockscreenShelf_useAmbientBottomPadding_returnsZero() {
-        enableSplitShade(/* enabled= */ false);
-        setBottomPadding(/* stackScrollLayoutBottom= */ 100,
-                /* lockIconPadding= */ 0,
-                /* indicationPadding= */ 0,
-                /* ambientPadding= */ 40);
-
-        when(mNotificationStackScrollLayoutController.getShelfHeight()).thenReturn(5);
-        assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
-                .isEqualTo(0);
-
-        assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
-                .isEqualTo(0);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void getVerticalSpaceForLockscreenShelf_useLockIconPadding_returnsLessThanShelfHeight() {
-        enableSplitShade(/* enabled= */ false);
-        setBottomPadding(/* stackScrollLayoutBottom= */ 100,
-                /* lockIconPadding= */ 10,
-                /* indicationPadding= */ 8,
-                /* ambientPadding= */ 0);
-
-        when(mNotificationStackScrollLayoutController.getShelfHeight()).thenReturn(5);
-        assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
-                .isEqualTo(2);
-
-        assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
-                .isEqualTo(2);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void getVerticalSpaceForLockscreenShelf_splitShade() {
-        enableSplitShade(/* enabled= */ true);
-        setBottomPadding(/* stackScrollLayoutBottom= */ 100,
-                /* lockIconPadding= */ 10,
-                /* indicationPadding= */ 8,
-                /* ambientPadding= */ 0);
-
-        when(mNotificationStackScrollLayoutController.getShelfHeight()).thenReturn(5);
-        assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
-                .isEqualTo(0);
-
-        assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
-                .isEqualTo(0);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testSetPanelScrimMinFractionWhenHeadsUpIsDragged() {
-        mNotificationPanelViewController.setHeadsUpDraggingStartingHeight(
-                mNotificationPanelViewController.getMaxPanelHeight() / 2);
-        verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f));
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testSetDozing_notifiesNsslAndStateController() {
-        mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */);
-        verify(mNotificationStackScrollLayoutController).setDozing(eq(true), eq(false));
-        assertThat(mStatusBarStateController.getDozeAmount()).isEqualTo(1f);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testOnDozeAmountChanged_positionClockAndNotificationsUsesUdfpsLocation() {
-        // GIVEN UDFPS is enrolled and we're on the keyguard
-        final Point udfpsLocationCenter = new Point(0, 100);
-        final float udfpsRadius = 10f;
-        when(mUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
-        when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocationCenter);
-        when(mAuthController.getUdfpsRadius()).thenReturn(udfpsRadius);
-        mNotificationPanelViewController.getStatusBarStateListener().onStateChanged(KEYGUARD);
-
-        // WHEN the doze amount changes
-        mNotificationPanelViewController.mClockPositionAlgorithm = mock(
-                KeyguardClockPositionAlgorithm.class);
-        mNotificationPanelViewController.getStatusBarStateListener().onDozeAmountChanged(1f, 1f);
-
-        // THEN the clock positions accounts for the UDFPS location & its worst case burn in
-        final float udfpsTop = udfpsLocationCenter.y - udfpsRadius - mMaxUdfpsBurnInOffsetY;
-        verify(mNotificationPanelViewController.mClockPositionAlgorithm).setup(
-                anyInt(),
-                anyFloat(),
-                anyInt(),
-                anyInt(),
-                anyInt(),
-                /* darkAmount */ eq(1f),
-                anyFloat(),
-                anyBoolean(),
-                anyInt(),
-                anyFloat(),
-                anyInt(),
-                anyBoolean(),
-                /* udfpsTop */ eq(udfpsTop),
-                anyFloat(),
-                anyBoolean()
-        );
-    }
-
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testSetExpandedHeight() {
-        mNotificationPanelViewController.setExpandedHeight(200);
-        assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testOnTouchEvent_expansionCanBeBlocked() {
-        onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
-        onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 200f, 0));
-        assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
-
-        mNotificationPanelViewController.blockExpansionForCurrentTouch();
-        onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 300f, 0));
-        // Expansion should not have changed because it was blocked
-        assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
-    }
-
-    @Test
     @EnableFlags(com.android.systemui.Flags.FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS)
     public void onStatusBarLongPress_shadeExpands() {
         long downTime = 42L;
@@ -422,71 +151,6 @@
     }
 
     @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void test_pulsing_onTouchEvent_noTracking() {
-        // GIVEN device is pulsing
-        mNotificationPanelViewController.setPulsing(true);
-
-        // WHEN touch DOWN & MOVE events received
-        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
-                0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
-                0 /* metaState */));
-        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
-                0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
-                0 /* metaState */));
-
-        // THEN touch is NOT tracked (since the device is pulsing)
-        assertThat(mNotificationPanelViewController.isTracking()).isFalse();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void alternateBouncerVisible_onTouchEvent_notHandled() {
-        // GIVEN alternate bouncer is visible
-        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
-
-        // WHEN touch DOWN event received; THEN touch is NOT handled
-        assertThat(onTouchEvent(MotionEvent.obtain(0L /* downTime */,
-                0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
-                0 /* metaState */))).isFalse();
-
-        // WHEN touch MOVE event received; THEN touch is NOT handled
-        assertThat(onTouchEvent(MotionEvent.obtain(0L /* downTime */,
-                0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
-                0 /* metaState */))).isFalse();
-
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void test_onTouchEvent_startTracking() {
-        // GIVEN device is NOT pulsing
-        mNotificationPanelViewController.setPulsing(false);
-
-        // WHEN touch DOWN & MOVE events received
-        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
-                0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
-                0 /* metaState */));
-        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
-                0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
-                0 /* metaState */));
-
-        // THEN touch is tracked
-        assertThat(mNotificationPanelViewController.isTracking()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onInterceptTouchEvent_nsslMigrationOff_userActivity() {
-        mTouchHandler.onInterceptTouchEvent(MotionEvent.obtain(0L /* downTime */,
-                0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
-                0 /* metaState */));
-
-        verify(mCentralSurfaces).userActivity();
-    }
-
-    @Test
-    @EnableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void onInterceptTouchEvent_nsslMigrationOn_userActivity_not_called() {
         mTouchHandler.onInterceptTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
@@ -496,279 +160,6 @@
     }
 
     @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testOnTouchEvent_expansionResumesAfterBriefTouch() {
-        mFalsingManager.setIsClassifierEnabled(true);
-        mFalsingManager.setIsFalseTouch(false);
-        mNotificationPanelViewController.setForceFlingAnimationForTest(true);
-        // Start shade collapse with swipe up
-        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
-                0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
-                0 /* metaState */));
-        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
-                0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */,
-                0 /* metaState */));
-        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
-                0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
-                0 /* metaState */));
-
-        assertThat(mNotificationPanelViewController.isClosing()).isTrue();
-        assertThat(mNotificationPanelViewController.isFlinging()).isTrue();
-
-        // simulate touch that does not exceed touch slop
-        onTouchEvent(MotionEvent.obtain(2L /* downTime */,
-                2L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 300f /* y */,
-                0 /* metaState */));
-
-        mNotificationPanelViewController.setTouchSlopExceeded(false);
-
-        onTouchEvent(MotionEvent.obtain(2L /* downTime */,
-                2L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
-                0 /* metaState */));
-
-        // fling should still be called after a touch that does not exceed touch slop
-        assertThat(mNotificationPanelViewController.isClosing()).isTrue();
-        assertThat(mNotificationPanelViewController.isFlinging()).isTrue();
-        mNotificationPanelViewController.setForceFlingAnimationForTest(false);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testA11y_initializeNode() {
-        AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
-        mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
-
-        List<AccessibilityNodeInfo.AccessibilityAction> actionList = nodeInfo.getActionList();
-        assertThat(actionList).containsAtLeastElementsIn(
-                new AccessibilityNodeInfo.AccessibilityAction[] {
-                        AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD,
-                        AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP}
-        );
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testA11y_scrollForward() {
-        mAccessibilityDelegate.performAccessibilityAction(
-                mView,
-                AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId(),
-                null);
-
-        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testA11y_scrollUp() {
-        mAccessibilityDelegate.performAccessibilityAction(
-                mView,
-                AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId(),
-                null);
-
-        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications() {
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        mNotificationPanelViewController.updateResources();
-        assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
-                .isEqualTo(R.id.qs_edge_guideline);
-
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
-        mNotificationPanelViewController.updateResources();
-        assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
-                .isEqualTo(ConstraintSet.PARENT_ID);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void keyguardStatusView_splitShade_dozing_alwaysDozingOn_isCentered() {
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-
-        setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ true);
-
-        assertKeyguardStatusViewCentered();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void keyguardStatusView_splitShade_dozing_alwaysDozingOff_isNotCentered() {
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-
-        setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ false);
-
-        assertKeyguardStatusViewNotCentered();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void keyguardStatusView_splitShade_notDozing_alwaysDozingOn_isNotCentered() {
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-
-        setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true);
-
-        assertKeyguardStatusViewNotCentered();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void keyguardStatusView_splitShade_pulsing_isNotCentered() {
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(true);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-
-        setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
-
-        assertKeyguardStatusViewNotCentered();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void keyguardStatusView_splitShade_notPulsing_isNotCentered() {
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(false);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-
-        setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
-
-        assertKeyguardStatusViewNotCentered();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void keyguardStatusView_singleShade_isCentered() {
-        enableSplitShade(/* enabled= */ false);
-        // The conditions below would make the clock NOT be centered on split shade.
-        // On single shade it should always be centered though.
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(false);
-        mStatusBarStateController.setState(KEYGUARD);
-        setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
-
-        assertKeyguardStatusViewCentered();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenNot() {
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-
-        mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
-        setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
-        assertKeyguardStatusViewCentered();
-
-        mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(false);
-        assertKeyguardStatusViewNotCentered();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() {
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-
-        mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
-
-        verify(mKeyguardMediaController).setDozeWakeUpAnimationWaiting(true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() {
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-
-        mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true);
-        setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
-        assertKeyguardStatusViewCentered();
-
-        mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(false);
-        assertKeyguardStatusViewCentered();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onKeyguardStatusViewHeightChange_animatesNextTopPaddingChangeForNSSL() {
-        ArgumentCaptor<View.OnLayoutChangeListener> captor =
-                ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
-        verify(mKeyguardStatusView).addOnLayoutChangeListener(captor.capture());
-        View.OnLayoutChangeListener listener = captor.getValue();
-
-        clearInvocations(mNotificationStackScrollLayoutController);
-
-        when(mKeyguardStatusView.getHeight()).thenReturn(0);
-        listener.onLayoutChange(mKeyguardStatusView, /* left= */ 0, /* top= */ 0, /* right= */
-                0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
-                0, /* oldBottom = */ 200);
-
-        verify(mNotificationStackScrollLayoutController).animateNextTopPaddingChange();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
-        mStatusBarStateController.setState(KEYGUARD);
-
-        assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testCanCollapsePanelOnTouch_trueWhenScrolledToBottom() {
-        mStatusBarStateController.setState(SHADE);
-        when(mNotificationStackScrollLayoutController.isScrolledToBottom()).thenReturn(true);
-
-        assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testCanCollapsePanelOnTouch_trueWhenInSettings() {
-        mStatusBarStateController.setState(SHADE);
-        when(mQsController.getExpanded()).thenReturn(true);
-
-        assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testCanCollapsePanelOnTouch_falseInDualPaneShade() {
-        mStatusBarStateController.setState(SHADE);
-        enableSplitShade(/* enabled= */ true);
-        when(mQsController.getExpanded()).thenReturn(true);
-
-        assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isFalse();
-    }
-
-    @Test
     @Ignore("b/341163515 - fails to clean up animators correctly")
     public void testSwipeWhileLocked_notifiesKeyguardState() {
         mStatusBarStateController.setState(KEYGUARD);
@@ -786,515 +177,6 @@
     }
 
     @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testCancelSwipeWhileLocked_notifiesKeyguardState() {
-        mStatusBarStateController.setState(KEYGUARD);
-
-        // Fling expanded (cancelling the keyguard exit swipe). We should notify keyguard state that
-        // the fling occurred and did not dismiss the keyguard.
-        mNotificationPanelViewController.flingToHeight(
-                0f, true /* expand */, 1000f, 1f, false);
-        mNotificationPanelViewController.cancelHeightAnimator();
-        verify(mKeyguardStateController).notifyPanelFlingEnd();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testSwipe_exactlyToTarget_notifiesNssl() {
-        // No over-expansion
-        mNotificationPanelViewController.setOverExpansion(0f);
-        // Fling to a target that is equal to the current position (i.e. a no-op fling).
-        mNotificationPanelViewController.flingToHeight(
-                0f,
-                true,
-                mNotificationPanelViewController.getExpandedHeight(),
-                1f,
-                false);
-        // Verify that the NSSL is notified that the panel is *not* flinging.
-        verify(mNotificationStackScrollLayoutController).setPanelFlinging(false);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testRotatingToSplitShadeWithQsExpanded_transitionsToShadeLocked() {
-        mStatusBarStateController.setState(KEYGUARD);
-        when(mQsController.getExpanded()).thenReturn(true);
-
-        enableSplitShade(true);
-
-        assertThat(mStatusBarStateController.getState()).isEqualTo(SHADE_LOCKED);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() {
-        enableSplitShade(true);
-        mStatusBarStateController.setState(SHADE);
-        mStatusBarStateController.setState(KEYGUARD);
-
-        verify(mQsController).closeQs();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testLockedSplitShadeTransitioningToKeyguard_closesQS() {
-        enableSplitShade(true);
-        mStatusBarStateController.setState(SHADE_LOCKED);
-        mStatusBarStateController.setState(KEYGUARD);
-
-        verify(mQsController).closeQs();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testSwitchesToCorrectClockInSinglePaneShade() {
-        mStatusBarStateController.setState(KEYGUARD);
-
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
-        triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
-
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testSwitchesToCorrectClockInSplitShade() {
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-        clearInvocations(mKeyguardStatusViewController);
-
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
-        triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
-
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController, times(2))
-                .displayClock(LARGE, /* animate */ true);
-        verify(mKeyguardStatusViewController, never())
-                .displayClock(SMALL, /* animate */ true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testHasNotifications_switchesToLargeClockWhenEnteringSplitShade() {
-        mStatusBarStateController.setState(KEYGUARD);
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-
-        enableSplitShade(/* enabled= */ true);
-
-        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testNoNotifications_switchesToLargeClockWhenEnteringSplitShade() {
-        mStatusBarStateController.setState(KEYGUARD);
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
-
-        enableSplitShade(/* enabled= */ true);
-
-        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testHasNotifications_switchesToSmallClockWhenExitingSplitShade() {
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-        clearInvocations(mKeyguardStatusViewController);
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-
-        enableSplitShade(/* enabled= */ false);
-
-        verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testNoNotifications_switchesToLargeClockWhenExitingSplitShade() {
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-        clearInvocations(mKeyguardStatusViewController);
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
-
-        enableSplitShade(/* enabled= */ false);
-
-        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void clockSize_mediaShowing_inSplitShade_onAod_isLarge() {
-        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-        when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        clearInvocations(mKeyguardStatusViewController);
-
-        mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false);
-
-        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate= */ true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void clockSize_mediaShowing_inSplitShade_screenOff_notAod_isSmall() {
-        when(mDozeParameters.getAlwaysOn()).thenReturn(false);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-        when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        clearInvocations(mKeyguardStatusViewController);
-
-        mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false);
-
-        verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate= */ true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_showNSSL() {
-        // GIVEN
-        mStatusBarStateController.setState(KEYGUARD);
-        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
-        when(mQsController.getFullyExpanded()).thenReturn(true);
-        when(mQsController.getExpanded()).thenReturn(true);
-
-        // WHEN
-        int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
-        mNotificationPanelViewController.setExpandedHeight(transitionDistance);
-
-        // THEN
-        // We are interested in the last value of the stack alpha.
-        ArgumentCaptor<Float> alphaCaptor = ArgumentCaptor.forClass(Float.class);
-        verify(mNotificationStackScrollLayoutController, atLeastOnce())
-                .setMaxAlphaForKeyguard(alphaCaptor.capture(), any());
-        assertThat(alphaCaptor.getValue()).isEqualTo(1.0f);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_hideNSSL() {
-        // GIVEN
-        mStatusBarStateController.setState(KEYGUARD);
-        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
-        when(mQsController.getFullyExpanded()).thenReturn(false);
-        when(mQsController.getExpanded()).thenReturn(true);
-
-        // WHEN
-        int transitionDistance = mNotificationPanelViewController
-                .getMaxPanelTransitionDistance() / 2;
-        mNotificationPanelViewController.setExpandedHeight(transitionDistance);
-
-        // THEN
-        // We are interested in the last value of the stack alpha.
-        ArgumentCaptor<Float> alphaCaptor = ArgumentCaptor.forClass(Float.class);
-        verify(mNotificationStackScrollLayoutController, atLeastOnce())
-                .setMaxAlphaForKeyguard(alphaCaptor.capture(), any());
-        assertThat(alphaCaptor.getValue()).isEqualTo(0.0f);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() {
-        when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-        clearInvocations(mKeyguardStatusViewController);
-        when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-
-        mNotificationPanelViewController.setDozing(true, false);
-
-        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ false);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void switchesToBigClockInSplitShadeOn_landFlagOn_ForceSmallClock() {
-        when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ false);
-        mNotificationPanelViewController.setDozing(false, false);
-        mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, true);
-        when(mResources.getBoolean(R.bool.force_small_clock_on_lockscreen)).thenReturn(true);
-        when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
-        clearInvocations(mKeyguardStatusViewController);
-
-        enableSplitShade(/* enabled= */ true);
-        mNotificationPanelViewController.updateResources();
-
-        verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ false);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void switchesToBigClockInSplitShadeOn_landFlagOff_DontForceSmallClock() {
-        when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ false);
-        mNotificationPanelViewController.setDozing(false, false);
-        mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false);
-        when(mResources.getBoolean(R.bool.force_small_clock_on_lockscreen)).thenReturn(true);
-        when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
-        clearInvocations(mKeyguardStatusViewController);
-
-        enableSplitShade(/* enabled= */ true);
-        mNotificationPanelViewController.updateResources();
-
-        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ false);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testDisplaysSmallClockOnLockscreenInSplitShadeWhenMediaIsPlaying() {
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(/* enabled= */ true);
-        clearInvocations(mKeyguardStatusViewController);
-        when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
-
-        // one notification + media player visible
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
-        triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
-
-        // only media player visible
-        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
-        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
-        triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL, true);
-        verify(mKeyguardStatusViewController, never()).displayClock(LARGE, /* animate */ true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testFoldToAodAnimationCleansupInAnimationEnd() {
-        ArgumentCaptor<Animator.AnimatorListener> animCaptor =
-                ArgumentCaptor.forClass(Animator.AnimatorListener.class);
-        ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> updateCaptor =
-                ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
-
-        // Start fold animation & Capture Listeners
-        mNotificationPanelViewController.getShadeFoldAnimator()
-                .startFoldToAodAnimation(() -> {}, () -> {}, () -> {});
-        verify(mViewPropertyAnimator).setListener(animCaptor.capture());
-        verify(mViewPropertyAnimator).setUpdateListener(updateCaptor.capture());
-
-        // End animation and validate listeners were unset
-        animCaptor.getValue().onAnimationEnd(null);
-        verify(mViewPropertyAnimator).setListener(null);
-        verify(mViewPropertyAnimator).setUpdateListener(null);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testExpandWithQsMethodIsUsingLockscreenTransitionController() {
-        enableSplitShade(/* enabled= */ true);
-        mStatusBarStateController.setState(KEYGUARD);
-
-        mNotificationPanelViewController.expandToQs();
-
-        verify(mLockscreenShadeTransitionController).goToLockedShade(
-                /* expandedView= */null, /* needsQSAnimation= */true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void setKeyguardStatusBarAlpha_setsAlphaOnKeyguardStatusBarController() {
-        float statusBarAlpha = 0.5f;
-
-        mNotificationPanelViewController.setKeyguardStatusBarAlpha(statusBarAlpha);
-
-        verify(mKeyguardStatusBarViewController).setAlpha(statusBarAlpha);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() {
-        enableSplitShade(/* enabled= */ true);
-        mShadeExpansionStateManager.updateState(STATE_OPEN);
-        verify(mQsController).setExpandImmediate(false);
-
-        mShadeExpansionStateManager.updateState(STATE_CLOSED);
-        verify(mQsController, times(2)).setExpandImmediate(false);
-
-        mShadeExpansionStateManager.updateState(STATE_OPENING);
-        verify(mQsController).setExpandImmediate(true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked() {
-        enableSplitShade(/* enabled= */ true);
-        mShadeExpansionStateManager.updateState(STATE_CLOSED);
-
-        mStatusBarStateController.setState(KEYGUARD);
-        // going to lockscreen would trigger STATE_OPENING
-        mShadeExpansionStateManager.updateState(STATE_OPENING);
-
-        verify(mQsController, never()).setExpandImmediate(true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testQsImmediateResetsWhenPanelOpensOrCloses() {
-        mShadeExpansionStateManager.updateState(STATE_OPEN);
-        mShadeExpansionStateManager.updateState(STATE_CLOSED);
-        verify(mQsController, times(2)).setExpandImmediate(false);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade() {
-        when(mCommandQueue.panelsEnabled()).thenReturn(true);
-
-        // to make sure shade is in expanded state
-        mNotificationPanelViewController.startInputFocusTransfer();
-
-        // switch to split shade from portrait (default state)
-        enableSplitShade(/* enabled= */ true);
-        verify(mQsController).setExpanded(true);
-
-        // switch to portrait from split shade
-        enableSplitShade(/* enabled= */ false);
-        verify(mQsController).setExpanded(false);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void testPanelClosedWhenClosingQsInSplitShade() {
-        mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
-                /* expanded= */ true, /* tracking= */ false);
-        enableSplitShade(/* enabled= */ true);
-        mNotificationPanelViewController.setExpandedFraction(1f);
-
-        assertThat(mNotificationPanelViewController.isClosing()).isFalse();
-        mNotificationPanelViewController.animateCollapseQs(false);
-
-        assertThat(mNotificationPanelViewController.isClosing()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void getMaxPanelTransitionDistance_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() {
-        enableSplitShade(true);
-        mNotificationPanelViewController.expandToQs();
-
-        int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
-
-        assertThat(maxDistance).isEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void isExpandingOrCollapsing_returnsTrue_whenQsLockscreenDragInProgress() {
-        when(mQsController.getLockscreenShadeDragProgress()).thenReturn(0.5f);
-        assertThat(mNotificationPanelViewController.isExpandingOrCollapsing()).isTrue();
-    }
-
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() {
-        enableSplitShade(true);
-        mNotificationPanelViewController.expandToQs();
-        when(mHeadsUpManager.isTrackingHeadsUp()).thenReturn(true);
-        when(mQsController.calculatePanelHeightExpanded(anyInt())).thenReturn(10000);
-        mNotificationPanelViewController.setHeadsUpDraggingStartingHeight(
-                SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
-
-        int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
-
-        // make sure we're ignoring the placeholder value for Qs max height
-        assertThat(maxDistance).isLessThan(10000);
-        assertThat(maxDistance).isGreaterThan(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void getMaxPanelTransitionDistance_expandingSplitShade_keyguard_returnsNonSplitShadeValue() {
-        mStatusBarStateController.setState(KEYGUARD);
-        enableSplitShade(true);
-        mNotificationPanelViewController.expandToQs();
-
-        int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
-
-        assertThat(maxDistance).isNotEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void getMaxPanelTransitionDistance_expanding_notSplitShade_returnsNonSplitShadeValue() {
-        enableSplitShade(false);
-        mNotificationPanelViewController.expandToQs();
-
-        int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
-
-        assertThat(maxDistance).isNotEqualTo(SPLIT_SHADE_FULL_TRANSITION_DISTANCE);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() {
-        setIsFullWidth(true);
-
-        verify(mQsController).setNotificationPanelFullWidth(true);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() {
-        setIsFullWidth(false);
-
-        verify(mQsController).setNotificationPanelFullWidth(false);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onLayoutChange_qsNotSet_doesNotCrash() {
-        mQuickSettingsController.setQs(null);
-
-        triggerLayoutChange();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() {
-        StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.getStatusBarStateListener();
-        statusBarStateListener.onStateChanged(KEYGUARD);
-        mNotificationPanelViewController.setDozing(false, false);
-
-        // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
-        mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
-        mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
-
-        verify(mDeviceEntryFaceAuthInteractor).onNotificationPanelClicked();
-    }
-
-    @Test
-    @EnableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     public void nsslFlagEnabled_allowOnlyExternalTouches() {
 
         // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
@@ -1306,130 +188,6 @@
     }
 
     @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onSplitShadeChanged_duringShadeExpansion_resetsOverScrollState() {
-        // There was a bug where there was left-over overscroll state after going from split shade
-        // to single shade.
-        // Since on single shade we don't set overscroll values on QS nor Scrim, those values that
-        // were there from split shade were never reset.
-        // To prevent this, we will reset all overscroll state.
-        enableSplitShade(true);
-        reset(mQsController, mScrimController, mNotificationStackScrollLayoutController);
-
-        mNotificationPanelViewController.setOverExpansion(123);
-        verify(mQsController).setOverScrollAmount(123);
-        verify(mScrimController).setNotificationsOverScrollAmount(123);
-        verify(mNotificationStackScrollLayoutController).setOverExpansion(123);
-
-        enableSplitShade(false);
-        verify(mQsController).setOverScrollAmount(0);
-        verify(mScrimController).setNotificationsOverScrollAmount(0);
-        verify(mNotificationStackScrollLayoutController).setOverExpansion(0);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onSplitShadeChanged_alwaysResetsOverScrollState() {
-        enableSplitShade(true);
-        enableSplitShade(false);
-
-        verify(mQsController, times(2)).setOverScrollAmount(0);
-        verify(mScrimController, times(2)).setNotificationsOverScrollAmount(0);
-        verify(mNotificationStackScrollLayoutController, times(2)).setOverExpansion(0);
-        verify(mNotificationStackScrollLayoutController, times(2)).setOverScrollAmount(0);
-    }
-
-    /**
-     * When shade is flinging to close and this fling is not intercepted,
-     * {@link AmbientState#setIsClosing(boolean)} should be called before
-     * {@link NotificationStackScrollLayoutController#onExpansionStopped()}
-     * to ensure scrollY can be correctly set to be 0
-     */
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() {
-        // Given: Shade is expanded
-        mNotificationPanelViewController.notifyExpandingFinished();
-        mNotificationPanelViewController.setClosing(false);
-
-        // When: Shade flings to close not canceled
-        mNotificationPanelViewController.notifyExpandingStarted();
-        mNotificationPanelViewController.setClosing(true);
-        mNotificationPanelViewController.onFlingEnd(false);
-
-        // Then: AmbientState's mIsClosing should be set to false
-        // before mNotificationStackScrollLayoutController.onExpansionStopped() is called
-        // to ensure NotificationStackScrollLayout.resetScrollPosition() -> resetScrollPosition
-        // -> setOwnScrollY(0) can set scrollY to 0 when shade is closed
-        InOrder inOrder = inOrder(mAmbientState, mNotificationStackScrollLayoutController);
-        inOrder.verify(mAmbientState).setIsClosing(false);
-        inOrder.verify(mNotificationStackScrollLayoutController).onExpansionStopped();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void onShadeFlingEnd_mExpandImmediateShouldBeReset() {
-        mNotificationPanelViewController.onFlingEnd(false);
-
-        verify(mQsController).setExpandImmediate(false);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void inUnlockedSplitShade_transitioningMaxTransitionDistance_makesShadeFullyExpanded() {
-        mStatusBarStateController.setState(SHADE);
-        enableSplitShade(true);
-        int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
-        mNotificationPanelViewController.setExpandedHeight(transitionDistance);
-        assertThat(mNotificationPanelViewController.isFullyExpanded()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void shadeFullyExpanded_inShadeState() {
-        mStatusBarStateController.setState(SHADE);
-
-        mNotificationPanelViewController.setExpandedHeight(0);
-        assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isFalse();
-
-        int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
-        mNotificationPanelViewController.setExpandedHeight(transitionDistance);
-        assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void shadeFullyExpanded_onKeyguard() {
-        mStatusBarStateController.setState(KEYGUARD);
-
-        int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
-        mNotificationPanelViewController.setExpandedHeight(transitionDistance);
-        assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isFalse();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void shadeFullyExpanded_onShadeLocked() {
-        mStatusBarStateController.setState(SHADE_LOCKED);
-        assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void shadeExpanded_whenHasHeight() {
-        int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
-        mNotificationPanelViewController.setExpandedHeight(transitionDistance);
-        assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void shadeExpanded_whenInstantExpanding() {
-        mNotificationPanelViewController.expand(true);
-        assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
-    }
-
-    @Test
     @DisableSceneContainer
     public void shadeExpanded_whenHunIsPresent() {
         when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
@@ -1437,84 +195,6 @@
     }
 
     @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void shadeExpanded_whenUnlockedOffscreenAnimationRunning() {
-        when(mUnlockedScreenOffAnimationController.isAnimationPlaying()).thenReturn(true);
-        assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void shadeExpanded_whenInputFocusTransferStarted() {
-        when(mCommandQueue.panelsEnabled()).thenReturn(true);
-
-        mNotificationPanelViewController.startInputFocusTransfer();
-
-        assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void shadeNotExpanded_whenInputFocusTransferStartedButPanelsDisabled() {
-        when(mCommandQueue.panelsEnabled()).thenReturn(false);
-
-        mNotificationPanelViewController.startInputFocusTransfer();
-
-        assertThat(mNotificationPanelViewController.isExpanded()).isFalse();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void cancelInputFocusTransfer_shadeCollapsed() {
-        when(mCommandQueue.panelsEnabled()).thenReturn(true);
-        mNotificationPanelViewController.startInputFocusTransfer();
-
-        mNotificationPanelViewController.cancelInputFocusTransfer();
-
-        assertThat(mNotificationPanelViewController.isExpanded()).isFalse();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void finishInputFocusTransfer_shadeFlingingOpen() {
-        when(mCommandQueue.panelsEnabled()).thenReturn(true);
-        mNotificationPanelViewController.startInputFocusTransfer();
-
-        mNotificationPanelViewController.finishInputFocusTransfer(/* velocity= */ 0f);
-
-        assertThat(mNotificationPanelViewController.isFlinging()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void getFalsingThreshold_deviceNotInteractive_isQsThreshold() {
-        PowerInteractor.Companion.setAsleepForTest(
-                mPowerInteractor, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
-        when(mQsController.getFalsingThreshold()).thenReturn(14);
-
-        assertThat(mNotificationPanelViewController.getFalsingThreshold()).isEqualTo(14);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void getFalsingThreshold_lastWakeNotDueToTouch_isQsThreshold() {
-        PowerInteractor.Companion.setAwakeForTest(
-                mPowerInteractor, PowerManager.WAKE_REASON_POWER_BUTTON);
-        when(mQsController.getFalsingThreshold()).thenReturn(14);
-
-        assertThat(mNotificationPanelViewController.getFalsingThreshold()).isEqualTo(14);
-    }
-
-    @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    public void getFalsingThreshold_lastWakeDueToTouch_greaterThanQsThreshold() {
-        PowerInteractor.Companion.setAwakeForTest(mPowerInteractor, PowerManager.WAKE_REASON_TAP);
-        when(mQsController.getFalsingThreshold()).thenReturn(14);
-
-        assertThat(mNotificationPanelViewController.getFalsingThreshold()).isGreaterThan(14);
-    }
-
-    @Test
     @EnableFlags(com.android.systemui.Flags.FLAG_MSDL_FEEDBACK)
     public void performHapticFeedback_withMSDL_forGestureStart_deliversDragThresholdToken() {
         mNotificationPanelViewController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
deleted file mode 100644
index 5289554..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.shade
-
-import android.platform.test.annotations.DisableFlags
-import android.testing.TestableLooper
-import android.view.HapticFeedbackConstants
-import android.view.View
-import android.view.ViewStub
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.util.CollectionUtils
-import com.android.keyguard.KeyguardClockSwitch.LARGE
-import com.android.systemui.Flags
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.StatusBarState.KEYGUARD
-import com.android.systemui.statusbar.StatusBarState.SHADE
-import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancelChildren
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.advanceUntilIdle
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Captor
-import org.mockito.Mockito.atLeastOnce
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-@DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-class NotificationPanelViewControllerWithCoroutinesTest :
-    NotificationPanelViewControllerBaseTest() {
-
-    @Captor private lateinit var viewCaptor: ArgumentCaptor<View>
-
-    override fun getMainDispatcher() = Dispatchers.Main.immediate
-
-    @Test
-    fun testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() = runTest {
-        launch(Dispatchers.Main.immediate) { givenViewAttached() }
-        advanceUntilIdle()
-
-        whenever(mResources.getBoolean(com.android.internal.R.bool.config_keyguardUserSwitcher))
-            .thenReturn(true)
-        updateMultiUserSetting(true)
-        clearInvocations(mView)
-
-        updateMultiUserSetting(false)
-
-        verify(mView, atLeastOnce()).addView(viewCaptor.capture(), anyInt())
-        val userSwitcherStub =
-            CollectionUtils.find(viewCaptor.allValues) { view ->
-                view.id == R.id.keyguard_user_switcher_stub
-            }
-        assertThat(userSwitcherStub).isNotNull()
-        assertThat(userSwitcherStub).isInstanceOf(ViewStub::class.java)
-    }
-
-    @Test
-    fun testChangeSmallestScreenWidthAndUserSwitchEnabled_inflatesUserSwitchView() = runTest {
-        launch(Dispatchers.Main.immediate) { givenViewAttached() }
-        advanceUntilIdle()
-
-        whenever(mView.findViewById<View>(R.id.keyguard_user_switcher_view)).thenReturn(null)
-        updateSmallestScreenWidth(300)
-        whenever(mResources.getBoolean(com.android.internal.R.bool.config_keyguardUserSwitcher))
-            .thenReturn(true)
-        whenever(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user))
-            .thenReturn(false)
-        whenever(mUserManager.isUserSwitcherEnabled(false)).thenReturn(true)
-
-        updateSmallestScreenWidth(800)
-
-        verify(mUserSwitcherStubView).inflate()
-    }
-
-    @Test
-    fun testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView_initClock() = runTest {
-        launch(Dispatchers.Main.immediate) { givenViewAttached() }
-        advanceUntilIdle()
-
-        whenever(mResources.getBoolean(com.android.internal.R.bool.config_keyguardUserSwitcher))
-            .thenReturn(true)
-        whenever(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user))
-            .thenReturn(false)
-        whenever(mUserManager.isUserSwitcherEnabled(false /* showEvenIfNotActionable */))
-            .thenReturn(false)
-
-        mNotificationPanelViewController.onFinishInflate()
-
-        verify(mUserSwitcherStubView, never()).inflate()
-        verify(mKeyguardStatusViewController, times(3)).displayClock(LARGE, /* animate */ true)
-
-        coroutineContext.cancelChildren()
-    }
-
-    @Test
-    fun testReInflateViews_userSwitcherDisabled_doNotInflateUserSwitchView() = runTest {
-        launch(Dispatchers.Main.immediate) { givenViewAttached() }
-        advanceUntilIdle()
-
-        whenever(mResources.getBoolean(com.android.internal.R.bool.config_keyguardUserSwitcher))
-            .thenReturn(true)
-        whenever(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user))
-            .thenReturn(false)
-        whenever(mUserManager.isUserSwitcherEnabled(false /* showEvenIfNotActionable */))
-            .thenReturn(false)
-
-        mNotificationPanelViewController.reInflateViews()
-
-        verify(mUserSwitcherStubView, never()).inflate()
-
-        coroutineContext.cancelChildren()
-    }
-
-    @Test
-    fun testDoubleTapRequired_Keyguard() = runTest {
-        launch(Dispatchers.Main.immediate) {
-            val listener = getFalsingTapListener()
-            mStatusBarStateController.setState(KEYGUARD)
-
-            listener.onAdditionalTapRequired()
-
-            verify(mKeyguardIndicationController).showTransientIndication(anyInt())
-        }
-        advanceUntilIdle()
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
-    fun doubleTapRequired_onKeyguard_usesPerformHapticFeedback() = runTest {
-        launch(Dispatchers.Main.immediate) {
-            val listener = getFalsingTapListener()
-            mStatusBarStateController.setState(KEYGUARD)
-
-            listener.onAdditionalTapRequired()
-            verify(mKeyguardIndicationController).showTransientIndication(anyInt())
-            verify(mVibratorHelper)
-                .performHapticFeedback(eq(mView), eq(HapticFeedbackConstants.REJECT))
-        }
-        advanceUntilIdle()
-    }
-
-    @Test
-    fun testDoubleTapRequired_ShadeLocked() = runTest {
-        launch(Dispatchers.Main.immediate) {
-            val listener = getFalsingTapListener()
-            mStatusBarStateController.setState(SHADE_LOCKED)
-
-            listener.onAdditionalTapRequired()
-
-            verify(mTapAgainViewController).show()
-        }
-        advanceUntilIdle()
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
-    fun doubleTapRequired_shadeLocked_usesPerformHapticFeedback() = runTest {
-        launch(Dispatchers.Main.immediate) {
-            val listener = getFalsingTapListener()
-            mStatusBarStateController.setState(SHADE_LOCKED)
-
-            listener.onAdditionalTapRequired()
-            verify(mVibratorHelper)
-                .performHapticFeedback(eq(mView), eq(HapticFeedbackConstants.REJECT))
-
-            verify(mTapAgainViewController).show()
-        }
-        advanceUntilIdle()
-    }
-
-    @Test
-    fun testOnAttachRefreshStatusBarState() = runTest {
-        launch(Dispatchers.Main.immediate) {
-            mStatusBarStateController.setState(KEYGUARD)
-            whenever(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(false)
-            mOnAttachStateChangeListeners.forEach { it.onViewAttachedToWindow(mView) }
-            verify(mKeyguardStatusViewController)
-                .setKeyguardStatusViewVisibility(
-                    KEYGUARD /*statusBarState*/,
-                    false /*keyguardFadingAway*/,
-                    false /*goingToFullShade*/,
-                    SHADE, /*oldStatusBarState*/
-                )
-        }
-        advanceUntilIdle()
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
index ad2b23e..a47db2e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt
@@ -118,12 +118,12 @@
         }
 
     @Test
-    fun getTopEdgeSplitFraction_wideScreen_leftSideLarger() =
+    fun getTopEdgeSplitFraction_wideScreen_splitInHalf() =
         testScope.runTest {
             // Ensure isShadeLayoutWide is collected.
             val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
             kosmos.shadeRepository.setShadeLayoutWide(true)
 
-            assertThat(underTest.getTopEdgeSplitFraction()).isGreaterThan(0.5f)
+            assertThat(underTest.getTopEdgeSplitFraction()).isEqualTo(0.5f)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt
new file mode 100644
index 0000000..d727089
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelperTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ui.view
+
+import android.view.View
+import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ChipTextTruncationHelperTest : SysuiTestCase() {
+
+    val underTest by lazy { ChipTextTruncationHelper(TextView(context)) }
+
+    @Before
+    fun setUp() {
+        mContext.getOrCreateTestableResources().apply {
+            this.addOverride(R.dimen.ongoing_activity_chip_max_text_width, MAX_WIDTH)
+        }
+    }
+
+    @Test
+    fun shouldShowText_desiredLessThanMax_true() {
+        val result =
+            underTest.shouldShowText(
+                desiredTextWidthPx = MAX_WIDTH / 2,
+                widthMeasureSpec = UNLIMITED_WIDTH_SPEC,
+            )
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun shouldShowText_desiredSlightlyLargerThanMax_true() {
+        val result =
+            underTest.shouldShowText(
+                desiredTextWidthPx = (MAX_WIDTH * 1.1).toInt(),
+                widthMeasureSpec = UNLIMITED_WIDTH_SPEC,
+            )
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun shouldShowText_desiredMoreThanTwiceMax_false() {
+        val result =
+            underTest.shouldShowText(
+                desiredTextWidthPx = (MAX_WIDTH * 2.2).toInt(),
+                widthMeasureSpec = UNLIMITED_WIDTH_SPEC,
+            )
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun shouldShowText_widthSpecLessThanMax_usesWidthSpec() {
+        val smallerWidthSpec =
+            SysuiMeasureSpec(
+                View.MeasureSpec.makeMeasureSpec(MAX_WIDTH / 2, View.MeasureSpec.AT_MOST)
+            )
+
+        // WHEN desired is more than twice the smallerWidthSpec
+        val desiredWidth = (MAX_WIDTH * 1.1).toInt()
+
+        val result =
+            underTest.shouldShowText(
+                desiredTextWidthPx = desiredWidth,
+                widthMeasureSpec = smallerWidthSpec,
+            )
+
+        // THEN returns false because smallerWidthSpec is used as the requirement
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun shouldShowText_maxLessThanWidthSpec_usesMax() {
+        val largerWidthSpec =
+            SysuiMeasureSpec(
+                View.MeasureSpec.makeMeasureSpec(MAX_WIDTH * 3, View.MeasureSpec.AT_MOST)
+            )
+
+        // WHEN desired is more than twice the max
+        val desiredWidth = (MAX_WIDTH * 2.2).toInt()
+
+        val result =
+            underTest.shouldShowText(
+                desiredTextWidthPx = desiredWidth,
+                widthMeasureSpec = largerWidthSpec,
+            )
+
+        // THEN returns false because the max is used as the requirement
+        assertThat(result).isFalse()
+    }
+
+    companion object {
+        private const val MAX_WIDTH = 200
+        private val UNLIMITED_WIDTH_SPEC =
+            SysuiMeasureSpec(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
index 1a7c8a3..43bd7cf0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
@@ -48,12 +48,14 @@
         val mySubCommand =
             object : ParseableCommand("subCommand") {
                 val flag by flag("flag")
+
                 override fun execute(pw: PrintWriter) {}
             }
 
         val mySubCommand2 =
             object : ParseableCommand("subCommand2") {
                 val flag by flag("flag")
+
                 override fun execute(pw: PrintWriter) {}
             }
 
@@ -141,6 +143,7 @@
         val cmd =
             object : ParseableCommand("test-command") {
                 val flag by flag("flag")
+
                 override fun execute(pw: PrintWriter) {}
             }
 
@@ -162,6 +165,7 @@
                 var onParseFailedCalled = false
 
                 override fun execute(pw: PrintWriter) {}
+
                 override fun onParseFailed(error: ArgParseError) {
                     onParseFailedCalled = true
                 }
@@ -204,11 +208,7 @@
         val cmd =
             object : ParseableCommand(name) {
                 val singleRequiredParam: String by
-                    param(
-                            longName = "param1",
-                            shortName = "p",
-                            valueParser = Type.String,
-                        )
+                    param(longName = "param1", shortName = "p", valueParser = Type.String)
                         .required()
 
                 override fun execute(pw: PrintWriter) {}
@@ -253,6 +253,7 @@
         val cmd =
             object : ParseableCommand(name) {
                 val subCmd by subCommand(subCmd)
+
                 override fun execute(pw: PrintWriter) {}
             }
 
@@ -293,18 +294,72 @@
         assertThat(myCommand.subCommand?.param1).isEqualTo("arg2")
     }
 
-    class MyCommand(
-        private val onExecute: ((MyCommand) -> Unit)? = null,
-    ) : ParseableCommand(name) {
+    @Test
+    fun commandWithSubCommand_allOptional_nothingPassed_execCalled() {
+        // GIVEN single sub command
+        val subName = "sub-command"
+        val subCmd =
+            object : ParseableCommand(subName) {
+                var execd = false
+
+                override fun execute(pw: PrintWriter) {
+                    execd = true
+                }
+            }
+
+        // GIVEN command wrapping the optional subcommand
+        val cmd =
+            object : ParseableCommand(name) {
+                val sub: ParseableCommand? by subCommand(subCmd)
+                var execCalled = false
+
+                override fun execute(pw: PrintWriter) {
+                    execCalled = true
+                }
+            }
+
+        // WHEN the base command is sent (i.e., sub-command is missing
+        cmd.execute(pw, listOf())
+        // THEN exec is still called, since this is a valid command
+        assertThat(cmd.execCalled).isTrue()
+    }
+
+    @Test
+    fun commandWithSubCommand_required_nothingPassed_execNotCalled() {
+        // GIVEN single sub command
+        val subName = "sub-command"
+        val subCmd =
+            object : ParseableCommand(subName) {
+                var execd = false
+
+                override fun execute(pw: PrintWriter) {
+                    execd = true
+                }
+            }
+
+        // GIVEN command wrapping the required subcommand
+        val cmd =
+            object : ParseableCommand(name) {
+                val sub: ParseableCommand? by subCommand(subCmd).required()
+                var execCalled = false
+
+                override fun execute(pw: PrintWriter) {
+                    execCalled = true
+                }
+            }
+
+        // WHEN the base command is sent (i.e., sub-command is missing
+        cmd.execute(pw, listOf())
+        // THEN exec is not called, since the subcommand is required
+        assertThat(cmd.execCalled).isFalse()
+    }
+
+    class MyCommand(private val onExecute: ((MyCommand) -> Unit)? = null) : ParseableCommand(name) {
 
         val flag1 by flag(shortName = "f", longName = "flag1", description = "flag 1 for test")
         val flag2 by flag(shortName = "g", longName = "flag2", description = "flag 2 for test")
         val singleParam: String? by
-            param(
-                shortName = "a",
-                longName = "arg1",
-                valueParser = Type.String,
-            )
+            param(shortName = "a", longName = "arg1", valueParser = Type.String)
 
         override fun execute(pw: PrintWriter) {
             onExecute?.invoke(this)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 2aeebe3..f25ba2c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -116,6 +116,7 @@
             }
         }
     private val promotedNotificationContentExtractor = FakePromotedNotificationContentExtractor()
+    private val conversationNotificationProcessor: ConversationNotificationProcessor = mock()
 
     @Before
     fun setUp() {
@@ -132,7 +133,7 @@
             NotificationRowContentBinderImpl(
                 cache,
                 mock(),
-                mock<ConversationNotificationProcessor>(),
+                conversationNotificationProcessor,
                 mock(),
                 smartReplyStateInflater,
                 layoutInflaterFactoryProvider,
@@ -462,12 +463,15 @@
     @Test
     fun testInflatePublicSingleLineConversationView() {
         val testPerson = Person.Builder().setName("Person").build()
+        val style = Notification.MessagingStyle(testPerson)
         val messagingBuilder =
             Notification.Builder(mContext, "no-id")
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle("Title")
                 .setContentText("Text")
-                .setStyle(Notification.MessagingStyle(testPerson))
+                .setStyle(style)
+        whenever(conversationNotificationProcessor.processNotification(any(), any(), any()))
+            .thenReturn(style)
 
         val messagingRow = spy(testHelper.createRow(messagingBuilder.build()))
         messagingRow.publicLayout.removeAllViews()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index c3c5a48..b0b80a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -18,9 +18,14 @@
 
 import android.app.PendingIntent
 import android.content.Intent
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.shared.Flags as SharedFlags
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -32,6 +37,9 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -54,6 +62,62 @@
             )
     }
 
+    @EnableFlags(
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @EnableSceneContainer
+    @Test
+    fun registerTransition_forwardsTheRequest() {
+        val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+        val controllerFactory = mock(ActivityTransitionAnimator.ControllerFactory::class.java)
+
+        underTest.registerTransition(cookie, controllerFactory)
+
+        verify(activityStarterInternal).registerTransition(eq(cookie), eq(controllerFactory))
+    }
+
+    @DisableFlags(
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @Test
+    fun registerTransition_doesNotForwardTheRequest_whenFlaggedOff() {
+        val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+        val controllerFactory = mock(ActivityTransitionAnimator.ControllerFactory::class.java)
+
+        underTest.registerTransition(cookie, controllerFactory)
+
+        verify(activityStarterInternal, never()).registerTransition(any(), any())
+    }
+
+    @EnableFlags(
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @EnableSceneContainer
+    @Test
+    fun unregisterTransition_forwardsTheRequest() {
+        val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+
+        underTest.unregisterTransition(cookie)
+
+        verify(activityStarterInternal).unregisterTransition(eq(cookie))
+    }
+
+    @DisableFlags(
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @Test
+    fun unregisterTransition_doesNotForwardTheRequest_whenFlaggedOff() {
+        val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+
+        underTest.unregisterTransition(cookie)
+
+        verify(activityStarterInternal, never()).unregisterTransition(any())
+    }
+
     @Test
     fun postStartActivityDismissingKeyguard_pendingIntent_postsOnMain() {
         val intent = mock(PendingIntent::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index bac79a9..5406acf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.shade.data.repository.ShadeAnimationRepository
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
+import com.android.systemui.shared.Flags as SharedFlags
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -63,6 +64,7 @@
 import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -149,6 +151,63 @@
         `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(false))
     }
 
+    @EnableFlags(
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @Test
+    fun registerTransition_registers() {
+        val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+        val controllerFactory = mock(ActivityTransitionAnimator.ControllerFactory::class.java)
+        `when`(controllerFactory.cookie).thenReturn(cookie)
+
+        underTest.registerTransition(cookie, controllerFactory)
+
+        verify(activityTransitionAnimator).register(eq(cookie), any())
+    }
+
+    @DisableFlags(
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @Test
+    fun registerTransition_throws_whenFlagsAreDisabled() {
+        val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+        val controllerFactory = mock(ActivityTransitionAnimator.ControllerFactory::class.java)
+
+        assertThrows(IllegalStateException::class.java) {
+            underTest.registerTransition(cookie, controllerFactory)
+        }
+
+        verify(activityTransitionAnimator, never()).register(any(), any())
+    }
+
+    @EnableFlags(
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @Test
+    fun unregisterTransition_unregisters() {
+        val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+
+        underTest.unregisterTransition(cookie)
+
+        verify(activityTransitionAnimator).unregister(eq(cookie))
+    }
+
+    @DisableFlags(
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+        SharedFlags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+    )
+    @Test
+    fun unregisterTransition_throws_whenFlagsAreDisabled() {
+        val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+
+        assertThrows(IllegalStateException::class.java) { underTest.unregisterTransition(cookie) }
+
+        verify(activityTransitionAnimator, never()).unregister(eq(cookie))
+    }
+
     @Test
     fun startActivityDismissingKeyguard_dismissShadeWhenOccluded_runAfterKeyguardGone() {
         val intent = mock(Intent::class.java)
@@ -216,7 +275,7 @@
         underTest.startPendingIntentDismissingKeyguard(
             intent = pendingIntent,
             dismissShade = true,
-            customMessage = customMessage
+            customMessage = customMessage,
         )
         mainExecutor.runAllReady()
 
@@ -296,7 +355,7 @@
                 nullable(PendingIntent.OnFinished::class.java),
                 nullable(Handler::class.java),
                 nullable(String::class.java),
-                bundleCaptor.capture()
+                bundleCaptor.capture(),
             )
         val options = ActivityOptions.fromBundle(bundleCaptor.firstValue)
         assertThat(options.getPendingIntentBackgroundActivityStartMode())
@@ -339,7 +398,7 @@
             dismissShade = true,
             animationController = controller,
             showOverLockscreen = true,
-            skipLockscreenChecks = true
+            skipLockscreenChecks = true,
         )
         mainExecutor.runAllReady()
 
@@ -373,7 +432,7 @@
             dismissShade = true,
             animationController = controller,
             showOverLockscreen = true,
-            skipLockscreenChecks = true
+            skipLockscreenChecks = true,
         )
         mainExecutor.runAllReady()
 
@@ -413,7 +472,7 @@
             dismissShade = false,
             animationController = controller,
             showOverLockscreen = true,
-            skipLockscreenChecks = false
+            skipLockscreenChecks = false,
         )
         mainExecutor.runAllReady()
 
@@ -458,7 +517,7 @@
             dismissShade = false,
             animationController = controller,
             showOverLockscreen = true,
-            skipLockscreenChecks = false
+            skipLockscreenChecks = false,
         )
         mainExecutor.runAllReady()
 
@@ -583,7 +642,7 @@
             },
             {},
             false,
-            customMessage
+            customMessage,
         )
 
         verify(centralSurfaces).awakenDreams()
@@ -602,7 +661,7 @@
             cancelAction = null,
             dismissShade = false,
             afterKeyguardGone = false,
-            deferred = false
+            deferred = false,
         )
 
         verify(centralSurfaces, times(1)).awakenDreams()
@@ -620,7 +679,7 @@
             cancelAction = null,
             dismissShade = false,
             afterKeyguardGone = false,
-            deferred = false
+            deferred = false,
         )
 
         verify(centralSurfaces, never()).awakenDreams()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
deleted file mode 100644
index 41782a1..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE;
-import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK;
-import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.StatusBarManager;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.InitController;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.domain.interactor.PowerInteractor;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.QuickSettingsController;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
-import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition;
-import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
-import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter;
-import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor;
-import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-
-import java.util.List;
-import java.util.Set;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@RunWithLooper()
-public class StatusBarNotificationPresenterTest extends SysuiTestCase {
-    private StatusBarNotificationPresenter mStatusBarNotificationPresenter;
-    private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider =
-            mock(VisualInterruptionDecisionProvider.class);
-    private NotificationInterruptSuppressor mInterruptSuppressor;
-    private VisualInterruptionCondition mAlertsDisabledCondition;
-    private VisualInterruptionCondition mVrModeCondition;
-    private VisualInterruptionFilter mNeedsRedactionFilter;
-    private VisualInterruptionCondition mPanelsDisabledCondition;
-    private CommandQueue mCommandQueue;
-    private final ShadeController mShadeController = mock(ShadeController.class);
-    private final NotificationAlertsInteractor mNotificationAlertsInteractor =
-            mock(NotificationAlertsInteractor.class);
-    private final KeyguardStateController mKeyguardStateController =
-            mock(KeyguardStateController.class);
-
-    @Before
-    public void setup() {
-        mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext));
-        mDependency.injectTestDependency(StatusBarStateController.class,
-                mock(SysuiStatusBarStateController.class));
-        mDependency.injectTestDependency(ShadeController.class, mShadeController);
-        mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
-        mDependency.injectMockDependency(NotificationShadeWindowController.class);
-
-        when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(true);
-
-        createPresenter();
-        if (VisualInterruptionRefactor.isEnabled()) {
-            verifyAndCaptureSuppressors();
-        } else {
-            verifyAndCaptureLegacySuppressor();
-        }
-    }
-
-    @Test
-    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testInit_refactorDisabled() {
-        assertFalse(VisualInterruptionRefactor.isEnabled());
-        assertNull(mAlertsDisabledCondition);
-        assertNull(mVrModeCondition);
-        assertNull(mNeedsRedactionFilter);
-        assertNull(mPanelsDisabledCondition);
-        assertNotNull(mInterruptSuppressor);
-    }
-
-    @Test
-    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testInit_refactorEnabled() {
-        assertTrue(VisualInterruptionRefactor.isEnabled());
-        assertNotNull(mAlertsDisabledCondition);
-        assertNotNull(mVrModeCondition);
-        assertNotNull(mNeedsRedactionFilter);
-        assertNotNull(mPanelsDisabledCondition);
-        assertNull(mInterruptSuppressor);
-    }
-
-    @Test
-    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testNoSuppressHeadsUp_default_refactorDisabled() {
-        assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry()));
-    }
-
-    @Test
-    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testNoSuppressHeadsUp_default_refactorEnabled() {
-        assertFalse(mAlertsDisabledCondition.shouldSuppress());
-        assertFalse(mVrModeCondition.shouldSuppress());
-        assertFalse(mNeedsRedactionFilter.shouldSuppress(createNotificationEntry()));
-        assertFalse(mAlertsDisabledCondition.shouldSuppress());
-    }
-
-    @Test
-    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testSuppressHeadsUp_disabledStatusBar_refactorDisabled() {
-        mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0,
-                false /* animate */);
-        TestableLooper.get(this).processAllMessages();
-
-        assertTrue("The panel should suppress heads up while disabled",
-                mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry()));
-    }
-
-    @Test
-    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testSuppressHeadsUp_disabledStatusBar_refactorEnabled() {
-        mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0,
-                false /* animate */);
-        TestableLooper.get(this).processAllMessages();
-
-        assertTrue("The panel should suppress heads up while disabled",
-                mPanelsDisabledCondition.shouldSuppress());
-    }
-
-    @Test
-    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testSuppressHeadsUp_disabledNotificationShade_refactorDisabled() {
-        mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
-                false /* animate */);
-        TestableLooper.get(this).processAllMessages();
-
-        assertTrue("The panel should suppress interruptions while notification shade disabled",
-                mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry()));
-    }
-
-    @Test
-    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testSuppressHeadsUp_disabledNotificationShade_refactorEnabled() {
-        mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
-                false /* animate */);
-        TestableLooper.get(this).processAllMessages();
-
-        assertTrue("The panel should suppress interruptions while notification shade disabled",
-                mPanelsDisabledCondition.shouldSuppress());
-    }
-
-    @Test
-    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testPanelsDisabledConditionSuppressesPeek() {
-        final Set<VisualInterruptionType> types = mPanelsDisabledCondition.getTypes();
-        assertTrue(types.contains(PEEK));
-        assertFalse(types.contains(PULSE));
-        assertFalse(types.contains(BUBBLE));
-    }
-
-    @Test
-    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorDisabled() {
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mKeyguardStateController.isOccluded()).thenReturn(false);
-
-        assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(createFsiNotificationEntry()));
-    }
-
-    @Test
-    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorEnabled() {
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mKeyguardStateController.isOccluded()).thenReturn(false);
-
-        assertFalse(mNeedsRedactionFilter.shouldSuppress(createFsiNotificationEntry()));
-
-        final Set<VisualInterruptionType> types = mNeedsRedactionFilter.getTypes();
-        assertTrue(types.contains(PEEK));
-        assertFalse(types.contains(PULSE));
-        assertFalse(types.contains(BUBBLE));
-    }
-
-    @Test
-    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testSuppressInterruptions_vrMode_refactorDisabled() {
-        mStatusBarNotificationPresenter.mVrMode = true;
-
-        assertTrue("Vr mode should suppress interruptions",
-                mInterruptSuppressor.suppressAwakeInterruptions(createNotificationEntry()));
-    }
-
-    @Test
-    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testSuppressInterruptions_vrMode_refactorEnabled() {
-        mStatusBarNotificationPresenter.mVrMode = true;
-
-        assertTrue("Vr mode should suppress interruptions", mVrModeCondition.shouldSuppress());
-
-        final Set<VisualInterruptionType> types = mVrModeCondition.getTypes();
-        assertTrue(types.contains(PEEK));
-        assertFalse(types.contains(PULSE));
-        assertTrue(types.contains(BUBBLE));
-    }
-
-    @Test
-    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testSuppressInterruptions_statusBarAlertsDisabled_refactorDisabled() {
-        when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
-
-        assertTrue("When alerts aren't enabled, interruptions are suppressed",
-                mInterruptSuppressor.suppressInterruptions(createNotificationEntry()));
-    }
-
-    @Test
-    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
-    public void testSuppressInterruptions_statusBarAlertsDisabled_refactorEnabled() {
-        when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
-
-        assertTrue("When alerts aren't enabled, interruptions are suppressed",
-                mAlertsDisabledCondition.shouldSuppress());
-
-        final Set<VisualInterruptionType> types = mAlertsDisabledCondition.getTypes();
-        assertTrue(types.contains(PEEK));
-        assertTrue(types.contains(PULSE));
-        assertTrue(types.contains(BUBBLE));
-    }
-
-    private void createPresenter() {
-        final ShadeViewController shadeViewController = mock(ShadeViewController.class);
-
-        final NotificationShadeWindowView notificationShadeWindowView =
-                mock(NotificationShadeWindowView.class);
-        when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
-
-        NotificationStackScrollLayoutController stackScrollLayoutController =
-                mock(NotificationStackScrollLayoutController.class);
-        when(stackScrollLayoutController.getView()).thenReturn(
-                mock(NotificationStackScrollLayout.class));
-
-        final InitController initController = new InitController();
-
-        mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
-                mContext,
-                shadeViewController,
-                mock(PanelExpansionInteractor.class),
-                mock(QuickSettingsController.class),
-                mock(HeadsUpManager.class),
-                notificationShadeWindowView,
-                mock(ActivityStarter.class),
-                stackScrollLayoutController,
-                mock(DozeScrimController.class),
-                mock(NotificationShadeWindowController.class),
-                mock(DynamicPrivacyController.class),
-                mKeyguardStateController,
-                mNotificationAlertsInteractor,
-                mock(LockscreenShadeTransitionController.class),
-                mock(PowerInteractor.class),
-                mCommandQueue,
-                mock(NotificationLockscreenUserManager.class),
-                mock(SysuiStatusBarStateController.class),
-                mock(NotifShadeEventSource.class),
-                mock(NotificationMediaManager.class),
-                mock(NotificationGutsManager.class),
-                initController,
-                mVisualInterruptionDecisionProvider,
-                mock(NotificationRemoteInputManager.class),
-                mock(NotificationRemoteInputManager.Callback.class),
-                mock(NotificationListContainer.class));
-
-        initController.executePostInitTasks();
-    }
-
-    private void verifyAndCaptureSuppressors() {
-        mInterruptSuppressor = null;
-
-        final ArgumentCaptor<VisualInterruptionCondition> conditionCaptor =
-                ArgumentCaptor.forClass(VisualInterruptionCondition.class);
-        verify(mVisualInterruptionDecisionProvider, times(3)).addCondition(
-                conditionCaptor.capture());
-        final List<VisualInterruptionCondition> conditions = conditionCaptor.getAllValues();
-        mAlertsDisabledCondition = conditions.get(0);
-        mVrModeCondition = conditions.get(1);
-        mPanelsDisabledCondition = conditions.get(2);
-
-        final ArgumentCaptor<VisualInterruptionFilter> needsRedactionFilterCaptor =
-                ArgumentCaptor.forClass(VisualInterruptionFilter.class);
-        verify(mVisualInterruptionDecisionProvider).addFilter(needsRedactionFilterCaptor.capture());
-        mNeedsRedactionFilter = needsRedactionFilterCaptor.getValue();
-    }
-
-    private void verifyAndCaptureLegacySuppressor() {
-        mAlertsDisabledCondition = null;
-        mVrModeCondition = null;
-        mNeedsRedactionFilter = null;
-        mPanelsDisabledCondition = null;
-
-        final ArgumentCaptor<NotificationInterruptSuppressor> suppressorCaptor =
-                ArgumentCaptor.forClass(NotificationInterruptSuppressor.class);
-        verify(mVisualInterruptionDecisionProvider).addLegacySuppressor(suppressorCaptor.capture());
-        mInterruptSuppressor = suppressorCaptor.getValue();
-    }
-
-    private NotificationEntry createNotificationEntry() {
-        return new NotificationEntryBuilder()
-                .setPkg("a")
-                .setOpPkg("a")
-                .setTag("a")
-                .setNotification(new Notification.Builder(getContext(), "a").build())
-                .build();
-    }
-
-    private NotificationEntry createFsiNotificationEntry() {
-        final Notification notification = new Notification.Builder(getContext(), "a")
-                .setFullScreenIntent(mock(PendingIntent.class), true)
-                .build();
-
-        return new NotificationEntryBuilder()
-                .setPkg("a")
-                .setOpPkg("a")
-                .setTag("a")
-                .setNotification(notification)
-                .build();
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt
new file mode 100644
index 0000000..baea1a1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.app.Notification
+import android.app.Notification.Builder
+import android.app.PendingIntent
+import android.app.StatusBarManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.InitController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
+import com.android.systemui.shade.notificationShadeWindowView
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.lockscreenShadeTransitionController
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.domain.interactor.notificationAlertsInteractor
+import com.android.systemui.statusbar.notification.dynamicPrivacyController
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.visualInterruptionDecisionProvider
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.statusbar.notificationRemoteInputManager
+import com.android.systemui.statusbar.notificationShadeWindowController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.statusbar.sysuiStatusBarStateController
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+class StatusBarNotificationPresenterTest : SysuiTestCase() {
+    private val kosmos: Kosmos =
+        testKosmos().apply {
+            whenever(notificationShadeWindowView.resources).thenReturn(mContext.resources)
+            whenever(notificationStackScrollLayoutController.view).thenReturn(mock())
+            whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(true)
+            commandQueue = CommandQueue(mContext, FakeDisplayTracker(mContext))
+
+            // override this controller with a mock, otherwise it would start some animators which
+            // are not cleaned up after these tests
+            lockscreenShadeTransitionController = mock()
+        }
+
+    // initiated by argumentCaptors later in the setup step, based on the flag states
+    private var interruptSuppressor: NotificationInterruptSuppressor? = null
+    private var alertsDisabledCondition: VisualInterruptionCondition? = null
+    private var vrModeCondition: VisualInterruptionCondition? = null
+    private var needsRedactionFilter: VisualInterruptionFilter? = null
+    private var panelsDisabledCondition: VisualInterruptionCondition? = null
+
+    private val commandQueue: CommandQueue = kosmos.commandQueue
+    private val keyguardStateController: KeyguardStateController = kosmos.keyguardStateController
+    private val notificationAlertsInteractor = kosmos.notificationAlertsInteractor
+    private val visualInterruptionDecisionProvider = kosmos.visualInterruptionDecisionProvider
+
+    private lateinit var underTest: StatusBarNotificationPresenter
+
+    @Before
+    fun setup() {
+        underTest = createPresenter()
+        if (VisualInterruptionRefactor.isEnabled) {
+            verifyAndCaptureSuppressors()
+        } else {
+            verifyAndCaptureLegacySuppressor()
+        }
+    }
+
+    @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testInit_refactorDisabled() {
+        assertThat(VisualInterruptionRefactor.isEnabled).isFalse()
+        assertThat(alertsDisabledCondition).isNull()
+        assertThat(vrModeCondition).isNull()
+        assertThat(needsRedactionFilter).isNull()
+        assertThat(panelsDisabledCondition).isNull()
+        assertThat(interruptSuppressor).isNotNull()
+    }
+
+    @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testInit_refactorEnabled() {
+        assertThat(VisualInterruptionRefactor.isEnabled).isTrue()
+        assertThat(alertsDisabledCondition).isNotNull()
+        assertThat(vrModeCondition).isNotNull()
+        assertThat(needsRedactionFilter).isNotNull()
+        assertThat(panelsDisabledCondition).isNotNull()
+        assertThat(interruptSuppressor).isNull()
+    }
+
+    @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testNoSuppressHeadsUp_default_refactorDisabled() {
+        assertThat(interruptSuppressor!!.suppressAwakeHeadsUp(createNotificationEntry())).isFalse()
+    }
+
+    @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testNoSuppressHeadsUp_default_refactorEnabled() {
+        assertThat(alertsDisabledCondition!!.shouldSuppress()).isFalse()
+        assertThat(vrModeCondition!!.shouldSuppress()).isFalse()
+        assertThat(needsRedactionFilter!!.shouldSuppress(createNotificationEntry())).isFalse()
+        assertThat(alertsDisabledCondition!!.shouldSuppress()).isFalse()
+    }
+
+    @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testSuppressHeadsUp_disabledStatusBar_refactorDisabled() {
+        commandQueue.disable(
+            /* displayId = */ DEFAULT_DISPLAY,
+            /* flags = */ StatusBarManager.DISABLE_EXPAND,
+            /* reason = */ 0,
+            /* animate = */ false,
+        )
+        TestableLooper.get(this).processAllMessages()
+        assertWithMessage("The panel should suppress heads up while disabled")
+            .that(interruptSuppressor!!.suppressAwakeHeadsUp(createNotificationEntry()))
+            .isTrue()
+    }
+
+    @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testSuppressHeadsUp_disabledStatusBar_refactorEnabled() {
+        commandQueue.disable(
+            /* displayId = */ DEFAULT_DISPLAY,
+            /* flags = */ StatusBarManager.DISABLE_EXPAND,
+            /* reason = */ 0,
+            /* animate = */ false,
+        )
+        TestableLooper.get(this).processAllMessages()
+        assertWithMessage("The panel should suppress heads up while disabled")
+            .that(panelsDisabledCondition!!.shouldSuppress())
+            .isTrue()
+    }
+
+    @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testSuppressHeadsUp_disabledNotificationShade_refactorDisabled() {
+        commandQueue.disable(
+            /* displayId = */ DEFAULT_DISPLAY,
+            /* flags = */ 0,
+            /* reason = */ StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
+            /* animate = */ false,
+        )
+        TestableLooper.get(this).processAllMessages()
+        assertWithMessage(
+                "The panel should suppress interruptions while notification shade disabled"
+            )
+            .that(interruptSuppressor!!.suppressAwakeHeadsUp(createNotificationEntry()))
+            .isTrue()
+    }
+
+    @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testSuppressHeadsUp_disabledNotificationShade_refactorEnabled() {
+        commandQueue.disable(
+            /* displayId = */ DEFAULT_DISPLAY,
+            /* flags = */ 0,
+            /* reason = */ StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
+            /* animate = */ false,
+        )
+        TestableLooper.get(this).processAllMessages()
+        assertWithMessage(
+                "The panel should suppress interruptions while notification shade disabled"
+            )
+            .that(panelsDisabledCondition!!.shouldSuppress())
+            .isTrue()
+    }
+
+    @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testPanelsDisabledConditionSuppressesPeek() {
+        val types: Set<VisualInterruptionType> = panelsDisabledCondition!!.types
+        assertThat(types).contains(VisualInterruptionType.PEEK)
+        assertThat(types)
+            .containsNoneOf(VisualInterruptionType.BUBBLE, VisualInterruptionType.PULSE)
+    }
+
+    @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorDisabled() {
+        whenever(keyguardStateController.isShowing()).thenReturn(true)
+        whenever(keyguardStateController.isOccluded()).thenReturn(false)
+        assertThat(interruptSuppressor!!.suppressAwakeHeadsUp(createFsiNotificationEntry()))
+            .isFalse()
+    }
+
+    @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorEnabled() {
+        whenever(keyguardStateController.isShowing()).thenReturn(true)
+        whenever(keyguardStateController.isOccluded()).thenReturn(false)
+        assertThat(needsRedactionFilter!!.shouldSuppress(createFsiNotificationEntry())).isFalse()
+        val types: Set<VisualInterruptionType> = needsRedactionFilter!!.types
+        assertThat(types).contains(VisualInterruptionType.PEEK)
+        assertThat(types)
+            .containsNoneOf(VisualInterruptionType.BUBBLE, VisualInterruptionType.PULSE)
+    }
+
+    @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testSuppressInterruptions_vrMode_refactorDisabled() {
+        underTest.mVrMode = true
+        assertWithMessage("Vr mode should suppress interruptions")
+            .that(interruptSuppressor!!.suppressAwakeInterruptions(createNotificationEntry()))
+            .isTrue()
+    }
+
+    @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testSuppressInterruptions_vrMode_refactorEnabled() {
+        underTest.mVrMode = true
+        assertWithMessage("Vr mode should suppress interruptions")
+            .that(vrModeCondition!!.shouldSuppress())
+            .isTrue()
+        val types: Set<VisualInterruptionType> = vrModeCondition!!.types
+        assertThat(types).contains(VisualInterruptionType.PEEK)
+        assertThat(types).doesNotContain(VisualInterruptionType.PULSE)
+        assertThat(types).contains(VisualInterruptionType.BUBBLE)
+    }
+
+    @Test
+    @DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testSuppressInterruptions_statusBarAlertsDisabled_refactorDisabled() {
+        whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false)
+        assertWithMessage("When alerts aren't enabled, interruptions are suppressed")
+            .that(interruptSuppressor!!.suppressInterruptions(createNotificationEntry()))
+            .isTrue()
+    }
+
+    @Test
+    @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
+    fun testSuppressInterruptions_statusBarAlertsDisabled_refactorEnabled() {
+        whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false)
+        assertWithMessage("When alerts aren't enabled, interruptions are suppressed")
+            .that(alertsDisabledCondition!!.shouldSuppress())
+            .isTrue()
+        val types: Set<VisualInterruptionType> = alertsDisabledCondition!!.types
+        assertThat(types).contains(VisualInterruptionType.PEEK)
+        assertThat(types).contains(VisualInterruptionType.PULSE)
+        assertThat(types).contains(VisualInterruptionType.BUBBLE)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun testExpandSensitiveNotification_onLockScreen_opensShade() =
+        kosmos.runTest {
+            // Given we are on the keyguard
+            kosmos.sysuiStatusBarStateController.state = StatusBarState.KEYGUARD
+            // And the device is locked
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+
+            // When the user expands a sensitive Notification
+            val row = createRow()
+            val entry =
+                row.entry.apply { setSensitive(/* sensitive= */ true, /* deviceSensitive= */ true) }
+
+            underTest.onExpandClicked(entry, mock(), /* nowExpanded= */ true)
+
+            // Then we open the locked shade
+            verify(kosmos.lockscreenShadeTransitionController)
+                // Explicit parameters to avoid issues with Kotlin default arguments in Mockito
+                .goToLockedShade(row, true)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun testExpandSensitiveNotification_onLockedShade_showsBouncer() =
+        kosmos.runTest {
+            // Given we are on the locked shade
+            kosmos.sysuiStatusBarStateController.state = StatusBarState.SHADE_LOCKED
+            // And the device is locked
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+
+            // When the user expands a sensitive Notification
+            val entry =
+                createRow().entry.apply {
+                    setSensitive(/* sensitive= */ true, /* deviceSensitive= */ true)
+                }
+            underTest.onExpandClicked(entry, mock(), /* nowExpanded= */ true)
+
+            // Then we show the bouncer
+            verify(kosmos.activityStarter).dismissKeyguardThenExecute(any(), eq(null), eq(false))
+        }
+
+    private fun createPresenter(): StatusBarNotificationPresenter {
+        val initController: InitController = InitController()
+        return StatusBarNotificationPresenter(
+                /* context = */ mContext,
+                /* panel = */ mock(),
+                kosmos.panelExpansionInteractor,
+                /* quickSettingsController = */ mock(),
+                kosmos.headsUpManager,
+                kosmos.notificationShadeWindowView,
+                kosmos.activityStarter,
+                kosmos.notificationStackScrollLayoutController,
+                kosmos.dozeScrimController,
+                kosmos.notificationShadeWindowController,
+                kosmos.dynamicPrivacyController,
+                kosmos.keyguardStateController,
+                kosmos.notificationAlertsInteractor,
+                kosmos.lockscreenShadeTransitionController,
+                kosmos.powerInteractor,
+                kosmos.commandQueue,
+                kosmos.notificationLockscreenUserManager,
+                kosmos.sysuiStatusBarStateController,
+                /* notifShadeEventSource = */ mock(),
+                /* notificationMediaManager = */ mock(),
+                /* notificationGutsManager = */ mock(),
+                /* initController = */ initController,
+                kosmos.visualInterruptionDecisionProvider,
+                kosmos.notificationRemoteInputManager,
+                /* remoteInputManagerCallback = */ mock(),
+                /* notificationListContainer = */ mock(),
+                kosmos.deviceUnlockedInteractor,
+            )
+            .also { initController.executePostInitTasks() }
+    }
+
+    private fun verifyAndCaptureSuppressors() {
+        interruptSuppressor = null
+
+        val conditionCaptor = argumentCaptor<VisualInterruptionCondition>()
+        verify(visualInterruptionDecisionProvider, times(3)).addCondition(conditionCaptor.capture())
+
+        val conditions: List<VisualInterruptionCondition> = conditionCaptor.allValues
+        alertsDisabledCondition = conditions[0]
+        vrModeCondition = conditions[1]
+        panelsDisabledCondition = conditions[2]
+
+        val needsRedactionFilterCaptor = argumentCaptor<VisualInterruptionFilter>()
+        verify(visualInterruptionDecisionProvider).addFilter(needsRedactionFilterCaptor.capture())
+        needsRedactionFilter = needsRedactionFilterCaptor.lastValue
+    }
+
+    private fun verifyAndCaptureLegacySuppressor() {
+        alertsDisabledCondition = null
+        vrModeCondition = null
+        needsRedactionFilter = null
+        panelsDisabledCondition = null
+
+        val suppressorCaptor = argumentCaptor<NotificationInterruptSuppressor>()
+        verify(visualInterruptionDecisionProvider).addLegacySuppressor(suppressorCaptor.capture())
+        interruptSuppressor = suppressorCaptor.lastValue
+    }
+
+    private fun createRow(): ExpandableNotificationRow {
+        val row: ExpandableNotificationRow = mock()
+        val entry: NotificationEntry = createNotificationEntry()
+        whenever(row.entry).thenReturn(entry)
+        entry.row = row
+        return row
+    }
+
+    private fun createNotificationEntry(): NotificationEntry =
+        NotificationEntryBuilder()
+            .setPkg("a")
+            .setOpPkg("a")
+            .setTag("a")
+            .setNotification(Builder(mContext, "a").build())
+            .build()
+
+    private fun createFsiNotificationEntry(): NotificationEntry {
+        val notification: Notification =
+            Builder(mContext, "a")
+                .setFullScreenIntent(mock<PendingIntent>(), /* highPriority= */ true)
+                .build()
+        return NotificationEntryBuilder()
+            .setPkg("a")
+            .setOpPkg("a")
+            .setTag("a")
+            .setNotification(notification)
+            .build()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
deleted file mode 100644
index 9f74915..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2021 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.policy
-
-import android.content.Context
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.util.UserIcons
-import com.android.systemui.res.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.qs.tiles.UserDetailItemView
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.util.mockito.whenever
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-class KeyguardUserSwitcherAdapterTest : SysuiTestCase() {
-    @Mock
-    private lateinit var userSwitcherController: UserSwitcherController
-    @Mock
-    private lateinit var parent: ViewGroup
-    @Mock
-    private lateinit var keyguardUserDetailItemView: KeyguardUserDetailItemView
-    @Mock
-    private lateinit var otherView: View
-    @Mock
-    private lateinit var inflatedUserDetailItemView: KeyguardUserDetailItemView
-    @Mock
-    private lateinit var layoutInflater: LayoutInflater
-    @Mock
-    private lateinit var keyguardUserSwitcherController: KeyguardUserSwitcherController
-
-    private lateinit var adapter: KeyguardUserSwitcherController.KeyguardUserAdapter
-    private lateinit var picture: Bitmap
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        whenever(userSwitcherController.isUserSwitcherEnabled).thenReturn(true)
-
-        mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, layoutInflater)
-        `when`(layoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
-                .thenReturn(inflatedUserDetailItemView)
-        adapter = KeyguardUserSwitcherController.KeyguardUserAdapter(
-                mContext,
-                mContext.resources,
-                LayoutInflater.from(mContext),
-                userSwitcherController, keyguardUserSwitcherController)
-        picture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
-    }
-
-    /**
-     * Uses the KeyguardUserAdapter to create a UserDetailItemView where the convertView has an
-     * incompatible type
-     */
-    private fun createViewFromDifferentType(
-        isCurrentUser: Boolean,
-        isGuestUser: Boolean
-    ): UserDetailItemView? {
-        val user = createUserRecord(isCurrentUser, isGuestUser)
-        return adapter.createUserDetailItemView(otherView, parent, user)
-    }
-
-    /**
-     * Uses the KeyguardUserAdapter to create a UserDetailItemView where the convertView is an
-     * instance of KeyguardUserDetailItemView
-     */
-    private fun createViewFromSameType(
-        isCurrentUser: Boolean,
-        isGuestUser: Boolean
-    ): UserDetailItemView? {
-        val user = createUserRecord(isCurrentUser, isGuestUser)
-        return adapter.createUserDetailItemView(keyguardUserDetailItemView, parent, user)
-    }
-
-    @Test
-    fun shouldSetOnClickListener_notCurrentUser_notGuestUser_oldViewIsSameType() {
-        val v: UserDetailItemView? = createViewFromSameType(
-                isCurrentUser = false, isGuestUser = false)
-        assertNotNull(v)
-        verify(v)!!.setOnClickListener(adapter)
-    }
-
-    @Test
-    fun shouldSetOnClickListener_notCurrentUser_guestUser_oldViewIsSameType() {
-        val v: UserDetailItemView? = createViewFromSameType(
-                isCurrentUser = false, isGuestUser = true)
-        assertNotNull(v)
-        verify(v)!!.setOnClickListener(adapter)
-    }
-
-    @Test
-    fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsSameType() {
-        val v: UserDetailItemView? = createViewFromSameType(
-                isCurrentUser = true, isGuestUser = false)
-        assertNotNull(v)
-        verify(v)!!.setOnClickListener(adapter)
-    }
-
-    @Test
-    fun shouldSetOnClickListener_currentUser_guestUser_oldViewIsSameType() {
-        val v: UserDetailItemView? = createViewFromSameType(
-                isCurrentUser = true, isGuestUser = true)
-        assertNotNull(v)
-        verify(v)!!.setOnClickListener(adapter)
-    }
-
-    @Test
-    fun shouldSetOnClickListener_notCurrentUser_notGuestUser_oldViewIsDifferentType() {
-        val v: UserDetailItemView? = createViewFromDifferentType(
-                isCurrentUser = false, isGuestUser = false)
-        assertNotNull(v)
-        verify(v)!!.setOnClickListener(adapter)
-    }
-
-    @Test
-    fun shouldSetOnClickListener_notCurrentUser_guestUser_oldViewIsDifferentType() {
-        val v: UserDetailItemView? = createViewFromDifferentType(
-                isCurrentUser = false, isGuestUser = true)
-        assertNotNull(v)
-        verify(v)!!.setOnClickListener(adapter)
-    }
-
-    @Test
-    fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() {
-        val v: UserDetailItemView? = createViewFromDifferentType(
-                isCurrentUser = true, isGuestUser = false)
-        assertNotNull(v)
-        verify(v)!!.setOnClickListener(adapter)
-    }
-
-    @Test
-    fun shouldSetOnClickListener_currentUser_guestUser_oldViewIsDifferentType() {
-        val v: UserDetailItemView? = createViewFromDifferentType(
-                isCurrentUser = true, isGuestUser = true)
-        assertNotNull(v)
-        verify(v)!!.setOnClickListener(adapter)
-    }
-
-    @Test
-    fun testCurrentUserIsAlwaysFirst() {
-        `when`(userSwitcherController.users).thenReturn(arrayListOf(
-                createUserRecord(isCurrentUser = false, isGuestUser = false),
-                createUserRecord(isCurrentUser = true, isGuestUser = false),
-                createUserRecord(isCurrentUser = false, isGuestUser = true),
-                createUserRecord(isCurrentUser = false, isGuestUser = false)
-        ))
-
-        adapter.notifyDataSetChanged()
-        assertTrue("Expected current user to be first in list", adapter.getItem(0).isCurrent)
-        assertFalse("Did not expect current user in position 1", adapter.getItem(1).isCurrent)
-        assertFalse("Did not expect current user in position 2", adapter.getItem(2).isCurrent)
-        assertTrue("Expected guest user to remain in position 2", adapter.getItem(2).isGuest)
-        assertFalse("Did not expect current user in position 3", adapter.getItem(3).isCurrent)
-    }
-
-    private fun createUserRecord(isCurrentUser: Boolean, isGuestUser: Boolean) =
-        UserRecord(
-            UserInfo(0 /* id */, "name", 0 /* flags */),
-            picture,
-            isGuestUser,
-            isCurrentUser,
-            false /* isAddUser */,
-            false /* isRestricted */,
-            true /* isSwitchToEnabled */,
-            false /* isAddSupervisedUser */
-        )
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index d5651ec..e2f363b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -52,7 +52,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 class GuestUserInteractorTest : SysuiTestCase() {
 
     @Mock private lateinit var manager: UserManager
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt
index 3f995c6..87d782e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt
@@ -172,9 +172,9 @@
                 runCurrent()
 
                 assertThat(slidersModel!!.slider)
-                    .isEqualTo(VolumeDialogSliderType.Stream(AudioManager.STREAM_MUSIC))
+                    .isEqualTo(VolumeDialogSliderType.Stream(AudioManager.STREAM_SYSTEM))
                 assertThat(slidersModel!!.floatingSliders)
-                    .containsExactly(VolumeDialogSliderType.Stream(AudioManager.STREAM_SYSTEM))
+                    .containsExactly(VolumeDialogSliderType.Stream(AudioManager.STREAM_MUSIC))
             }
         }
     }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index abb721a..55be9f7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -16,6 +16,7 @@
 
 import android.annotation.Nullable;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -33,6 +34,22 @@
 public interface ActivityStarter {
     int VERSION = 2;
 
+    /**
+     * Registers the given {@link ActivityTransitionAnimator.ControllerFactory} for launching and
+     * closing transitions matching the {@link ActivityTransitionAnimator.TransitionCookie} and the
+     * {@link ComponentName} that it contains.
+     */
+    void registerTransition(
+            ActivityTransitionAnimator.TransitionCookie cookie,
+            ActivityTransitionAnimator.ControllerFactory controllerFactory);
+
+    /**
+     * Unregisters the {@link ActivityTransitionAnimator.ControllerFactory} previously registered
+     * containing the given {@link ActivityTransitionAnimator.TransitionCookie}. If no such
+     * registration exists, this is a no-op.
+     */
+    void unregisterTransition(ActivityTransitionAnimator.TransitionCookie cookie);
+
     void startPendingIntentDismissingKeyguard(PendingIntent intent);
 
     /**
diff --git a/packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt b/packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt
index 6b54d89..d93f7d3 100644
--- a/packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt
+++ b/packages/SystemUI/plugin_core/processor/src/com/android/systemui/plugins/processor/ProtectedPluginProcessor.kt
@@ -33,9 +33,10 @@
 
 /**
  * [ProtectedPluginProcessor] generates a proxy implementation for interfaces annotated with
- * [ProtectedInterface] which catches [LinkageError]s generated by the proxied target. This protects
- * the plugin host from crashing due to out-of-date plugin code, where some call has changed so that
- * the [ClassLoader] can no longer resolve it correctly.
+ * [ProtectedInterface] which catches [Exception]s generated by the proxied target. Production
+ * plugin interfaces should use this to catch [LinkagError]s as that protects the plugin host from
+ * crashing due to out-of-date plugin code, where some call has changed so that the [ClassLoader] is
+ * no longer able to resolve it correctly.
  *
  * [PluginInstance] observes these failures via [ProtectedMethodListener] and unloads the plugin in
  * question to prevent further issues. This persists through further load/unload requests.
@@ -61,6 +62,7 @@
         val sourcePkg: String,
         val sourceName: String,
         val outputName: String,
+        val exTypeAttr: ProtectedInterface,
     )
 
     override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
@@ -68,10 +70,19 @@
         val additionalImports = mutableSetOf<String>()
         for (attr in annotations) {
             for (target in roundEnv.getElementsAnnotatedWith(attr)) {
+                // Find the target exception types to be used
+                var exTypeAttr = target.getAnnotation(ProtectedInterface::class.java)
+                if (exTypeAttr == null || exTypeAttr.exTypes.size == 0) {
+                    exTypeAttr = ProtectedInterface.Default
+                }
+
                 val sourceName = "${target.simpleName}"
                 val outputName = "${sourceName}Protector"
                 val pkg = (target.getEnclosingElement() as PackageElement).qualifiedName.toString()
-                targets.put("$target", TargetData(attr, target, pkg, sourceName, outputName))
+                targets.put(
+                    "$target",
+                    TargetData(attr, target, pkg, sourceName, outputName, exTypeAttr),
+                )
 
                 // This creates excessive imports, but it should be fine
                 additionalImports.add("$pkg.$sourceName")
@@ -80,7 +91,7 @@
         }
 
         if (targets.size <= 0) return false
-        for ((_, sourceType, sourcePkg, sourceName, outputName) in targets.values) {
+        for ((_, sourceType, sourcePkg, sourceName, outputName, exTypeAttr) in targets.values) {
             // Find all methods in this type and all super types to that need to be implemented
             val types = ArrayDeque<TypeMirror>().apply { addLast(sourceType.asType()) }
             val impAttrs = mutableListOf<GeneratedImport>()
@@ -105,7 +116,6 @@
 
                 // Imports used by the proxy implementation
                 line("import android.util.Log;")
-                line("import java.lang.LinkageError;")
                 line("import com.android.systemui.plugins.PluginWrapper;")
                 line("import com.android.systemui.plugins.ProtectedPluginListener;")
                 line()
@@ -118,6 +128,14 @@
                     line()
                 }
 
+                // Imports of caught exceptions
+                if (exTypeAttr.exTypes.size > 0) {
+                    for (exType in exTypeAttr.exTypes) {
+                        line("import $exType;")
+                    }
+                    line()
+                }
+
                 // Imports declared via @GeneratedImport
                 if (impAttrs.size > 0) {
                     for (impAttr in impAttrs) {
@@ -232,11 +250,14 @@
                             // Protect callsite in try/catch block
                             braceBlock("try") { line(callStatement) }
 
-                            // Notify listener when a LinkageError is caught
-                            braceBlock("catch (LinkageError ex)") {
-                                line("Log.wtf(CLASS, \"Failed to execute: \" + METHOD, ex);")
-                                line("mHasError = mListener.onFail(CLASS, METHOD, ex);")
-                                line(errorStatement)
+                            // Notify listener when a target exception is caught
+                            for (exType in exTypeAttr.exTypes) {
+                                val simpleName = exType.substringAfterLast(".")
+                                braceBlock("catch ($simpleName ex)") {
+                                    line("Log.wtf(CLASS, \"Failed to execute: \" + METHOD, ex);")
+                                    line("mHasError = mListener.onFail(CLASS, METHOD, ex);")
+                                    line(errorStatement)
+                                }
                             }
                         }
                         line()
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/ProtectedPluginListener.kt b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/ProtectedPluginListener.kt
index 3a1f251..8e2528f 100644
--- a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/ProtectedPluginListener.kt
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/ProtectedPluginListener.kt
@@ -16,12 +16,11 @@
 /** Listener for events from proxy types generated by [ProtectedPluginProcessor]. */
 interface ProtectedPluginListener {
     /**
-     * Called when a method call produces a [LinkageError] before returning. This callback is
-     * provided so that the host application can terminate the plugin or log the error as
-     * appropriate.
+     * Called when a method call produces a [Exception] before returning. This callback is provided
+     * so that the host application can terminate the plugin or log the error as appropriate.
      *
      * @return true to terminate all methods within this object; false if the error is recoverable
      *   and the proxied plugin should continue to operate as normal.
      */
-    fun onFail(className: String, methodName: String, failure: LinkageError): Boolean
+    fun onFail(className: String, methodName: String, failure: Throwable): Boolean
 }
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/annotations/ProtectedInterface.kt b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/annotations/ProtectedInterface.kt
index 12a977d..dc2ea8c 100644
--- a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/annotations/ProtectedInterface.kt
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/annotations/ProtectedInterface.kt
@@ -15,11 +15,15 @@
 
 /**
  * This annotation marks denotes that an interface should use a proxy layer to protect the plugin
- * host from crashing due to [LinkageError]s originating within the plugin's implementation.
+ * host from crashing due to the [Exception] types originating within the plugin's implementation.
  */
 @Target(AnnotationTarget.CLASS)
 @Retention(AnnotationRetention.BINARY)
-annotation class ProtectedInterface
+annotation class ProtectedInterface(vararg val exTypes: String) {
+    companion object {
+        val Default = ProtectedInterface("java.lang.Exception", "java.lang.LinkageError")
+    }
+}
 
 /**
  * This annotation specifies any additional imports that the processor will require when generating
@@ -32,9 +36,9 @@
 annotation class GeneratedImport(val extraImport: String)
 
 /**
- * This annotation provides default values to return when the proxy implementation catches a
- * [LinkageError]. The string specified should be a simple but valid java statement. In most cases
- * it should be a return statement of the appropriate type, but in some cases throwing a known
+ * This annotation provides default values to return when the proxy implementation catches a target
+ * [Exception]. The string specified should be a simple but valid java statement. In most cases it
+ * should be a return statement of the appropriate type, but in some cases throwing a known
  * exception type may be preferred.
  *
  * This annotation is not required for methods that return void, but will behave the same way.
diff --git a/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
index dfefb9d..4b7be35 100644
--- a/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
+++ b/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
@@ -27,18 +27,7 @@
             <solid android:color="@android:color/transparent"/>
         </shape>
     </item>
-    <item
-        android:end="20dp"
-        android:gravity="end|center_vertical">
-        <vector
-            android:width="@dimen/hearing_devices_preset_spinner_icon_size"
-            android:height="@dimen/hearing_devices_preset_spinner_icon_size"
-            android:viewportWidth="24"
-            android:viewportHeight="24"
-            android:tint="?androidprv:attr/colorControlNormal">
-            <path
-                android:fillColor="#FF000000"
-                android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
-        </vector>
-    </item>
+    <item android:end="20dp"
+        android:gravity="end|center_vertical"
+        android:drawable="@drawable/ic_hearing_device_expand" />
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_hearing_device_expand.xml b/packages/SystemUI/res/drawable/ic_hearing_device_expand.xml
new file mode 100644
index 0000000..fdfe713
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_hearing_device_expand.xml
@@ -0,0 +1,27 @@
+<!--
+    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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@androidprv:color/materialColorOnSurface">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-sw600dp/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout-sw600dp/keyguard_user_switcher_item.xml
deleted file mode 100644
index 9ce030e..0000000
--- a/packages/SystemUI/res/layout-sw600dp/keyguard_user_switcher_item.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<!-- LinearLayout -->
-<com.android.systemui.statusbar.policy.KeyguardUserDetailItemView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:sysui="http://schemas.android.com/apk/res-auto"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:padding="8dp"
-        android:layout_marginEnd="32dp"
-        android:gravity="end|center_vertical"
-        android:clickable="true"
-        android:background="@drawable/kg_user_switcher_rounded_bg"
-        sysui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
-        sysui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher">
-    <TextView android:id="@+id/user_name"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="25dp"
-            android:layout_marginEnd="12dp"
-            />
-    <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/user_picture"
-            android:layout_width="@dimen/kg_framed_avatar_size"
-            android:layout_height="@dimen/kg_framed_avatar_size"
-            android:contentDescription="@null"
-            sysui:badgeDiameter="18dp"
-            sysui:badgeMargin="1dp" />
-</com.android.systemui.statusbar.policy.KeyguardUserDetailItemView>
diff --git a/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml b/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml
new file mode 100644
index 0000000..fd409a5
--- /dev/null
+++ b/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml
@@ -0,0 +1,67 @@
+<!--
+    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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/ambient_header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+        android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+        <ImageView
+            android:id="@+id/ambient_volume_icon"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="12dp"
+            android:contentDescription="@string/hearing_devices_ambient_unmute"
+            android:src="@drawable/ic_ambient_volume"
+            android:tint="@androidprv:color/materialColorOnSurface" />
+        <TextView
+            android:id="@+id/ambient_title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:paddingStart="10dp"
+            android:text="@string/hearing_devices_ambient_label"
+            android:textAppearance="@style/TextAppearance.Dialog.Title"
+            android:textDirection="locale"
+            android:textSize="16sp"
+            android:gravity="center_vertical"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium" />
+        <ImageView
+            android:id="@+id/ambient_expand_icon"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="10dp"
+            android:contentDescription="@string/hearing_devices_ambient_expand_controls"
+            android:src="@drawable/ic_hearing_device_expand"
+            android:tint="@androidprv:color/materialColorOnSurface" />
+    </LinearLayout>
+    <LinearLayout
+        android:id="@+id/ambient_control_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/hearing_device_ambient_volume_slider.xml b/packages/SystemUI/res/layout/hearing_device_ambient_volume_slider.xml
new file mode 100644
index 0000000..44ada89
--- /dev/null
+++ b/packages/SystemUI/res/layout/hearing_device_ambient_volume_slider.xml
@@ -0,0 +1,46 @@
+<!--
+    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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+    android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/ambient_volume_slider_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:paddingStart="@dimen/hearing_devices_small_title_padding_horizontal"
+        android:textAppearance="@style/TextAppearance.Dialog.Title"
+        android:textDirection="locale"
+        android:textSize="14sp"
+        android:labelFor="@+id/ambient_volume_slider"
+        android:gravity="center_vertical" />
+    <com.google.android.material.slider.Slider
+        style="@style/SystemUI.Material3.Slider"
+        android:id="@+id/ambient_volume_slider"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/bluetooth_dialog_device_height"
+        android:layout_gravity="center_vertical"
+        android:theme="@style/Theme.Material3.DayNight"
+        app:labelBehavior="gone" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
index bf04a6f..949a6ab 100644
--- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
@@ -85,13 +85,22 @@
             android:longClickable="false"/>
     </LinearLayout>
 
+    <com.android.systemui.accessibility.hearingaid.AmbientVolumeLayout
+        android:id="@+id/ambient_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/preset_layout"
+        android:layout_marginTop="@dimen/hearing_devices_layout_margin" />
+
     <LinearLayout
         android:id="@+id/tools_layout"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/preset_layout"
+        app:layout_constraintTop_toBottomOf="@id/ambient_layout"
         android:layout_marginTop="@dimen/hearing_devices_layout_margin"
         android:orientation="vertical">
         <TextView
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher.xml b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
deleted file mode 100644
index 7aafd89..0000000
--- a/packages/SystemUI/res/layout/keyguard_user_switcher.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2014 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
-  -->
-<!-- This is a view that shows a user switcher in Keyguard. -->
-<com.android.systemui.statusbar.policy.KeyguardUserSwitcherView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/keyguard_user_switcher_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_gravity="end">
-
-    <com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView
-        android:id="@+id/keyguard_user_switcher_list"
-        android:orientation="vertical"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        android:layout_gravity="top|end"
-        android:gravity="end" />
-
-</com.android.systemui.statusbar.policy.KeyguardUserSwitcherView>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
deleted file mode 100644
index e39f1a9..0000000
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ Copyright (C) 2014 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<!-- LinearLayout -->
-<com.android.systemui.statusbar.policy.KeyguardUserDetailItemView
-        android:id="@+id/user_item"
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:systemui="http://schemas.android.com/apk/res-auto"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:padding="8dp"
-        android:layout_marginEnd="8dp"
-        android:gravity="end|center_vertical"
-        android:clickable="true"
-        android:background="@drawable/kg_user_switcher_rounded_bg"
-        systemui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
-        systemui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher">
-    <TextView
-        android:id="@+id/user_name"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="20dp"
-        android:layout_marginEnd="16dp" />
-    <com.android.systemui.statusbar.phone.UserAvatarView
-        android:id="@+id/user_picture"
-        android:layout_width="@dimen/kg_framed_avatar_size"
-        android:layout_height="@dimen/kg_framed_avatar_size"
-        systemui:avatarPadding="0dp"
-        systemui:badgeDiameter="18dp"
-        systemui:badgeMargin="1dp"
-        systemui:frameWidth="0dp"
-        systemui:framePadding="0dp"
-        systemui:frameColor="@color/kg_user_avatar_frame" />
-</com.android.systemui.statusbar.policy.KeyguardUserDetailItemView>
diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
index 7745af9..51217d4 100644
--- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
@@ -58,14 +58,14 @@
         />
 
         <!-- Shows generic text. -->
-        <TextView
+        <com.android.systemui.statusbar.chips.ui.view.ChipTextView
             android:id="@+id/ongoing_activity_chip_text"
             style="@style/StatusBar.Chip.Text.LimitedWidth"
             android:visibility="gone"
             />
 
         <!-- Shows a time delta in short form, like "15min" or "1hr". -->
-        <android.widget.DateTimeView
+        <com.android.systemui.statusbar.chips.ui.view.ChipDateTimeView
             android:id="@+id/ongoing_activity_chip_short_time_delta"
             style="@style/StatusBar.Chip.Text.LimitedWidth"
             android:visibility="gone"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 77fbb64..46a9d47 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -31,12 +31,6 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
-    <ViewStub
-        android:id="@+id/keyguard_qs_user_switch_stub"
-        android:layout="@layout/keyguard_qs_user_switch"
-        android:layout_height="match_parent"
-        android:layout_width="match_parent" />
-
     <include layout="@layout/status_bar_expanded_plugin_frame"/>
 
     <com.android.systemui.shade.NotificationsQuickSettingsContainer
@@ -120,12 +114,6 @@
         />
     </com.android.systemui.shade.NotificationsQuickSettingsContainer>
 
-    <ViewStub
-        android:id="@+id/keyguard_user_switcher_stub"
-        android:layout="@layout/keyguard_user_switcher"
-        android:layout_height="match_parent"
-        android:layout_width="match_parent" />
-
     <include layout="@layout/dock_info_bottom_area_overlay" />
 
     <include
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 5ccedea..bad5711 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -71,6 +71,9 @@
         android:layout_height="0dp"
         android:layout_marginTop="@dimen/volume_dialog_floating_sliders_vertical_padding_negative"
         android:layout_marginBottom="@dimen/volume_dialog_floating_sliders_vertical_padding_negative"
+        android:clipChildren="false"
+        android:clipToOutline="false"
+        android:clipToPadding="false"
         android:divider="@drawable/volume_dialog_floating_sliders_spacer"
         android:gravity="bottom"
         android:orientation="horizontal"
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index d850bbe..cd8f18f 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -23,7 +23,6 @@
     android:clipToPadding="false"
     android:gravity="center"
     android:layoutDirection="ltr"
-    android:orientation="vertical"
     app:layoutDescription="@xml/volume_dialog_ringer_drawer_motion_scene">
 
     <!-- add ringer buttons here -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 35cfd08..05c4d1b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -2106,6 +2106,8 @@
 
     <fraction name="volume_dialog_half_opened_bias">0.2</fraction>
 
+    <dimen name="volume_dialog_slider_max_deviation">56dp</dimen>
+
     <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
 
     <dimen name="volume_dialog_ringer_drawer_button_size">@dimen/volume_dialog_button_size</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d3ee63ba..c3d84ff 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1004,6 +1004,20 @@
     <string name="hearing_devices_preset_label">Preset</string>
     <!-- QuickSettings: Content description for the icon that indicates the item is selected [CHAR LIMIT=NONE]-->
     <string name="hearing_devices_spinner_item_selected">Selected</string>
+    <!-- QuickSettings: Title for ambient controls. [CHAR LIMIT=40]-->
+    <string name="hearing_devices_ambient_label">Surroundings</string>
+    <!-- QuickSettings: The text to show the control is for left side device. [CHAR LIMIT=30] -->
+    <string name="hearing_devices_ambient_control_left">Left</string>
+    <!-- QuickSettings: The text to show the control is for right side device. [CHAR LIMIT=30] -->
+    <string name="hearing_devices_ambient_control_right">Right</string>
+    <!-- QuickSettings: Content description for a button, that expands ambient volume sliders [CHAR_LIMIT=NONE] -->
+    <string name="hearing_devices_ambient_expand_controls">Expand to left and right separated controls</string>
+    <!-- QuickSettings: Content description for a button, that collapses ambient volume sliders [CHAR LIMIT=NONE] -->
+    <string name="hearing_devices_ambient_collapse_controls">Collapse to unified control</string>
+    <!-- QuickSettings: Content description for a button, that mute ambient volume [CHAR_LIMIT=NONE] -->
+    <string name="hearing_devices_ambient_mute">Mute surroundings</string>
+    <!-- QuickSettings: Content description for a button, that unmute ambient volume [CHAR LIMIT=NONE] -->
+    <string name="hearing_devices_ambient_unmute">Unmute surroundings</string>
     <!-- QuickSettings: Title for related tools of hearing. [CHAR LIMIT=40]-->
     <string name="hearing_devices_tools_label">Tools</string>
     <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]-->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f6c1ecea..7180678 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -91,7 +91,6 @@
         <item name="android:ellipsize">none</item>
         <item name="android:requiresFadingEdge">horizontal</item>
         <item name="android:fadingEdgeLength">@dimen/ongoing_activity_chip_text_fading_edge_length</item>
-        <item name="android:maxWidth">@dimen/ongoing_activity_chip_max_text_width</item>
     </style>
 
     <style name="Chipbar" />
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 8298397..69f5a79 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -108,7 +108,8 @@
     }
 
     @Override
-    public synchronized boolean onFail(String className, String methodName, LinkageError failure) {
+    public synchronized boolean onFail(String className, String methodName, Throwable failure) {
+        Log.e(TAG, "Failure from " + mPlugin + ". Disabling Plugin.");
         mHasError = true;
         unloadPlugin();
         mListener.onPluginDetached(this);
@@ -118,7 +119,7 @@
     /** Alerts listener and plugin that the plugin has been created. */
     public synchronized void onCreate() {
         if (mHasError) {
-            log("Previous LinkageError detected for plugin class");
+            log("Previous Fatal Exception detected for plugin class");
             return;
         }
 
@@ -175,7 +176,7 @@
      */
     public synchronized void loadPlugin() {
         if (mHasError) {
-            log("Previous LinkageError detected for plugin class");
+            log("Previous Fatal Exception detected for plugin class");
             return;
         }
 
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index f9fe67a..c13bb3e 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -67,7 +67,7 @@
         namespace: String = "systemui",
         default: Boolean = false
     ): SysPropBooleanFlag {
-        val flag = SysPropBooleanFlag(name = name, namespace = "systemui", default = default)
+        val flag = SysPropBooleanFlag(name = name, namespace, default = default)
         checkForDupesAndAdd(flag)
         return flag
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
index 4331f52..a887011 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
@@ -29,7 +29,7 @@
 @Subcomponent(modules = {KeyguardUserSwitcherModule.class})
 @KeyguardUserSwitcherScope
 public interface KeyguardQsUserSwitchComponent {
-    /** Simple factory for {@link KeyguardUserSwitcherComponent}. */
+    /** Simple factory for {@link KeyguardQsUserSwitchComponent}. */
     @Subcomponent.Factory
     interface Factory {
         KeyguardQsUserSwitchComponent build(@BindsInstance FrameLayout userAvatarContainer);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java
deleted file mode 100644
index 730c14d..0000000
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2021 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.keyguard.dagger;
-
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
-
-import dagger.BindsInstance;
-import dagger.Subcomponent;
-
-/**
- * Subcomponent for helping work with KeyguardUserSwitcher and its children.
- */
-@Subcomponent(modules = {KeyguardUserSwitcherModule.class})
-@KeyguardUserSwitcherScope
-public interface KeyguardUserSwitcherComponent {
-    /** Simple factory for {@link KeyguardUserSwitcherComponent}. */
-    @Subcomponent.Factory
-    interface Factory {
-        KeyguardUserSwitcherComponent build(
-                @BindsInstance KeyguardUserSwitcherView keyguardUserSwitcherView);
-    }
-
-    /** Builds a {@link com.android.systemui.statusbar.policy.KeyguardUserSwitcherController}. */
-    KeyguardUserSwitcherController getKeyguardUserSwitcherController();
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
index b9184f4..1ad2d4c 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
@@ -18,7 +18,7 @@
 
 import dagger.Module;
 
-/** Dagger module for {@link KeyguardUserSwitcherComponent}. */
+/** Dagger module for {@link KeyguardQsUserSwitchComponent}. */
 @Module
 public abstract class KeyguardUserSwitcherModule {
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
index 864472e..12d1949 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
@@ -24,7 +24,7 @@
 import javax.inject.Scope;
 
 /**
- * Scope annotation for singleton items within the KeyguardUserSwitcherComponent.
+ * Scope annotation for singleton items within the KeyguardQsUserSwitchComponent.
  */
 @Documented
 @Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
new file mode 100644
index 0000000..7c141c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.bluetooth.AmbientVolumeUi;
+import com.android.systemui.res.R;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.primitives.Ints;
+
+import java.util.Map;
+
+/**
+ * A view of ambient volume controls.
+ *
+ * <p> It consists of a header with an expand icon and volume sliders for unified control and
+ * separated control for devices in the same set. Toggle the expand icon will make the UI switch
+ * between unified and separated control.
+ */
+public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi {
+
+    @Nullable
+    private AmbientVolumeUiListener mListener;
+    private ImageView mExpandIcon;
+    private ImageView mVolumeIcon;
+    private boolean mExpandable = true;
+    private boolean mExpanded = false;
+    private boolean mMutable = false;
+    private boolean mMuted = false;
+    private final BiMap<Integer, AmbientVolumeSlider> mSideToSliderMap = HashBiMap.create();
+    private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
+
+    private final AmbientVolumeSlider.OnChangeListener mSliderOnChangeListener =
+            (slider, value) -> {
+                if (mListener != null) {
+                    final int side = mSideToSliderMap.inverse().get(slider);
+                    mListener.onSliderValueChange(side, value);
+                }
+            };
+
+    public AmbientVolumeLayout(@Nullable Context context) {
+        this(context, /* attrs= */ null);
+    }
+
+    public AmbientVolumeLayout(@Nullable Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, /* defStyleAttr= */ 0);
+    }
+
+    public AmbientVolumeLayout(@Nullable Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        this(context, attrs, defStyleAttr, /* defStyleRes= */ 0);
+    }
+
+    public AmbientVolumeLayout(@Nullable Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        inflate(context, R.layout.hearing_device_ambient_volume_layout, /* root= */ this);
+        init();
+    }
+
+    private void init() {
+        mVolumeIcon = requireViewById(R.id.ambient_volume_icon);
+        mVolumeIcon.setImageResource(com.android.settingslib.R.drawable.ic_ambient_volume);
+        mVolumeIcon.setOnClickListener(v -> {
+            if (!mMutable) {
+                return;
+            }
+            setMuted(!mMuted);
+            if (mListener != null) {
+                mListener.onAmbientVolumeIconClick();
+            }
+        });
+        updateVolumeIcon();
+
+        mExpandIcon = requireViewById(R.id.ambient_expand_icon);
+        mExpandIcon.setOnClickListener(v -> {
+            setExpanded(!mExpanded);
+            if (mListener != null) {
+                mListener.onExpandIconClick();
+            }
+        });
+        updateExpandIcon();
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        setVisibility(visible ? VISIBLE : GONE);
+    }
+
+    @Override
+    public void setExpandable(boolean expandable) {
+        mExpandable = expandable;
+        if (!mExpandable) {
+            setExpanded(false);
+        }
+        updateExpandIcon();
+    }
+
+    @Override
+    public boolean isExpandable() {
+        return mExpandable;
+    }
+
+    @Override
+    public void setExpanded(boolean expanded) {
+        if (!mExpandable && expanded) {
+            return;
+        }
+        mExpanded = expanded;
+        updateExpandIcon();
+        updateLayout();
+    }
+
+    @Override
+    public boolean isExpanded() {
+        return mExpanded;
+    }
+
+    @Override
+    public void setMutable(boolean mutable) {
+        mMutable = mutable;
+        if (!mMutable) {
+            mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
+            setMuted(false);
+        }
+        updateVolumeIcon();
+    }
+
+    @Override
+    public boolean isMutable() {
+        return mMutable;
+    }
+
+    @Override
+    public void setMuted(boolean muted) {
+        if (!mMutable && muted) {
+            return;
+        }
+        mMuted = muted;
+        if (mMutable && mMuted) {
+            for (AmbientVolumeSlider slider : mSideToSliderMap.values()) {
+                slider.setValue(slider.getMin());
+            }
+        }
+        updateVolumeIcon();
+    }
+
+    @Override
+    public boolean isMuted() {
+        return mMuted;
+    }
+
+    @Override
+    public void setListener(@Nullable AmbientVolumeUiListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void setupSliders(@NonNull Map<Integer, BluetoothDevice> sideToDeviceMap) {
+        sideToDeviceMap.forEach((side, device) -> createSlider(side));
+        createSlider(SIDE_UNIFIED);
+
+        LinearLayout controlContainer = requireViewById(R.id.ambient_control_container);
+        controlContainer.removeAllViews();
+        if (!mSideToSliderMap.isEmpty()) {
+            for (int side : VALID_SIDES) {
+                final AmbientVolumeSlider slider = mSideToSliderMap.get(side);
+                if (slider != null) {
+                    controlContainer.addView(slider);
+                }
+            }
+        }
+        updateLayout();
+    }
+
+    @Override
+    public void setSliderEnabled(int side, boolean enabled) {
+        AmbientVolumeSlider slider = mSideToSliderMap.get(side);
+        if (slider != null && slider.isEnabled() != enabled) {
+            slider.setEnabled(enabled);
+            updateLayout();
+        }
+    }
+
+    @Override
+    public void setSliderValue(int side, int value) {
+        AmbientVolumeSlider slider = mSideToSliderMap.get(side);
+        if (slider != null && slider.getValue() != value) {
+            slider.setValue(value);
+            updateVolumeLevel();
+        }
+    }
+
+    @Override
+    public void setSliderRange(int side, int min, int max) {
+        AmbientVolumeSlider slider = mSideToSliderMap.get(side);
+        if (slider != null) {
+            slider.setMin(min);
+            slider.setMax(max);
+        }
+    }
+
+    @Override
+    public void updateLayout() {
+        mSideToSliderMap.forEach((side, slider) -> {
+            if (side == SIDE_UNIFIED) {
+                slider.setVisibility(mExpanded ? GONE : VISIBLE);
+            } else {
+                slider.setVisibility(mExpanded ? VISIBLE : GONE);
+            }
+            if (!slider.isEnabled()) {
+                slider.setValue(slider.getMin());
+            }
+        });
+        updateVolumeLevel();
+    }
+
+    private void updateVolumeLevel() {
+        int leftLevel, rightLevel;
+        if (mExpanded) {
+            leftLevel = getVolumeLevel(SIDE_LEFT);
+            rightLevel = getVolumeLevel(SIDE_RIGHT);
+        } else {
+            final int unifiedLevel = getVolumeLevel(SIDE_UNIFIED);
+            leftLevel = unifiedLevel;
+            rightLevel = unifiedLevel;
+        }
+        mVolumeLevel = Ints.constrainToRange(leftLevel * 5 + rightLevel,
+                AMBIENT_VOLUME_LEVEL_MIN, AMBIENT_VOLUME_LEVEL_MAX);
+        updateVolumeIcon();
+    }
+
+    private int getVolumeLevel(int side) {
+        AmbientVolumeSlider slider = mSideToSliderMap.get(side);
+        if (slider == null || !slider.isEnabled()) {
+            return 0;
+        }
+        return slider.getVolumeLevel();
+    }
+
+    private void updateExpandIcon() {
+        mExpandIcon.setVisibility(mExpandable ? VISIBLE : GONE);
+        mExpandIcon.setRotation(mExpanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED);
+        if (mExpandable) {
+            final int stringRes = mExpanded ? R.string.hearing_devices_ambient_collapse_controls
+                    : R.string.hearing_devices_ambient_expand_controls;
+            mExpandIcon.setContentDescription(mContext.getString(stringRes));
+        } else {
+            mExpandIcon.setContentDescription(null);
+        }
+    }
+
+    private void updateVolumeIcon() {
+        mVolumeIcon.setImageLevel(mMuted ? 0 : mVolumeLevel);
+        if (mMutable) {
+            final int stringRes = mMuted ? R.string.hearing_devices_ambient_unmute
+                    : R.string.hearing_devices_ambient_mute;
+            mVolumeIcon.setContentDescription(mContext.getString(stringRes));
+            mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }  else {
+            mVolumeIcon.setContentDescription(null);
+            mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+        }
+    }
+
+    private void createSlider(int side) {
+        if (mSideToSliderMap.containsKey(side)) {
+            return;
+        }
+        AmbientVolumeSlider slider = new AmbientVolumeSlider(mContext);
+        slider.addOnChangeListener(mSliderOnChangeListener);
+        if (side == SIDE_LEFT) {
+            slider.setTitle(mContext.getString(R.string.hearing_devices_ambient_control_left));
+        } else if (side == SIDE_RIGHT) {
+            slider.setTitle(mContext.getString(R.string.hearing_devices_ambient_control_right));
+        }
+        mSideToSliderMap.put(side, slider);
+    }
+
+    @VisibleForTesting
+    ImageView getVolumeIcon() {
+        return mVolumeIcon;
+    }
+
+    @VisibleForTesting
+    ImageView getExpandIcon() {
+        return mExpandIcon;
+    }
+
+    @VisibleForTesting
+    Map<Integer, AmbientVolumeSlider> getSliders() {
+        return mSideToSliderMap;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSlider.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSlider.java
new file mode 100644
index 0000000..92338ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSlider.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.res.R;
+
+import com.google.android.material.slider.Slider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A view of ambient volume slider.
+ * <p> It consists by a title {@link TextView} with a volume control {@link Slider}.
+ */
+public class AmbientVolumeSlider extends LinearLayout {
+
+    private final TextView mTitle;
+    private final Slider mSlider;
+    private final List<OnChangeListener> mChangeListeners = new ArrayList<>();
+    private final Slider.OnSliderTouchListener mSliderTouchListener =
+            new Slider.OnSliderTouchListener() {
+                @Override
+                public void onStartTrackingTouch(@NonNull Slider slider) {
+                }
+
+                @Override
+                public void onStopTrackingTouch(@NonNull Slider slider) {
+                    final int value = Math.round(slider.getValue());
+                    for (OnChangeListener listener : mChangeListeners) {
+                        listener.onValueChange(AmbientVolumeSlider.this, value);
+                    }
+                }
+            };
+    public AmbientVolumeSlider(@Nullable Context context) {
+        this(context, /* attrs= */ null);
+    }
+
+    public AmbientVolumeSlider(@Nullable Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, /* defStyleAttr= */ 0);
+    }
+
+    public AmbientVolumeSlider(@Nullable Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        this(context, attrs, defStyleAttr, /* defStyleRes= */ 0);
+    }
+
+    public AmbientVolumeSlider(@Nullable Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        inflate(context, R.layout.hearing_device_ambient_volume_slider, /* root= */ this);
+        mTitle = requireViewById(R.id.ambient_volume_slider_title);
+        mSlider = requireViewById(R.id.ambient_volume_slider);
+        mSlider.addOnSliderTouchListener(mSliderTouchListener);
+    }
+
+    /**
+     * Sets title for the ambient volume slider.
+     * <p> If text is null or empty, then {@link TextView} is hidden.
+     */
+    public void setTitle(@Nullable String text) {
+        mTitle.setText(text);
+        mTitle.setVisibility(TextUtils.isEmpty(text) ? GONE : VISIBLE);
+    }
+
+    /** Gets title for the ambient volume slider. */
+    public CharSequence getTitle() {
+        return mTitle.getText();
+    }
+
+    /**
+     * Adds the callback to the ambient volume slider to get notified when the value is changed by
+     * user.
+     * <p> Note: The {@link OnChangeListener#onValueChange(AmbientVolumeSlider, int)} will be
+     * called when user's finger take off from the slider.
+     */
+    public void addOnChangeListener(@Nullable OnChangeListener listener) {
+        if (listener == null) {
+            return;
+        }
+        mChangeListeners.add(listener);
+    }
+
+    /** Sets max value to the ambient volume slider. */
+    public void setMax(float max) {
+        mSlider.setValueTo(max);
+    }
+
+    /** Gets max value from the ambient volume slider. */
+    public float getMax() {
+        return mSlider.getValueTo();
+    }
+
+    /** Sets min value to the ambient volume slider. */
+    public void setMin(float min) {
+        mSlider.setValueFrom(min);
+    }
+
+    /** Gets min value from the ambient volume slider. */
+    public float getMin() {
+        return mSlider.getValueFrom();
+    }
+
+    /** Sets value to the ambient volume slider. */
+    public void setValue(float value) {
+        mSlider.setValue(value);
+    }
+
+    /** Gets value from the ambient volume slider. */
+    public float getValue() {
+        return mSlider.getValue();
+    }
+
+    /** Sets the enable state to the ambient volume slider. */
+    public void setEnabled(boolean enabled) {
+        mSlider.setEnabled(enabled);
+    }
+
+    /** Gets the enable state of the ambient volume slider. */
+    public boolean isEnabled() {
+        return mSlider.isEnabled();
+    }
+
+    /**
+     * Gets the volume value of the ambient volume slider.
+     * <p> The volume level is divided into 5 levels:
+     * Level 0 corresponds to the minimum volume value. The range between the minimum and maximum
+     * volume is divided into 4 equal intervals, represented by levels 1 to 4.
+     */
+    public int getVolumeLevel() {
+        if (!mSlider.isEnabled()) {
+            return 0;
+        }
+        final double min = mSlider.getValueFrom();
+        final double max = mSlider.getValueTo();
+        final double levelGap = (max - min) / 4.0;
+        final double value = mSlider.getValue();
+        return (int) Math.ceil((value - min) / levelGap);
+    }
+
+    /** Interface definition for a callback invoked when a slider's value is changed. */
+    public interface OnChangeListener {
+        /** Called when the finger is take off from the slider. */
+        void onValueChange(@NonNull AmbientVolumeSlider slider, int value);
+    }
+}
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 56435df..73aabc3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -52,10 +52,12 @@
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.settingslib.bluetooth.AmbientVolumeUiController;
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.bluetooth.qsdialog.ActiveHearingDeviceItemFactory;
@@ -108,7 +110,6 @@
 
     private SystemUIDialog mDialog;
 
-    private RecyclerView mDeviceList;
     private List<DeviceItem> mHearingDeviceItemList;
     private HearingDevicesListAdapter mDeviceListAdapter;
 
@@ -134,6 +135,8 @@
                 }
             };
 
+    private AmbientVolumeUiController mAmbientController;
+
     private final List<DeviceItemFactory> mHearingDeviceItemFactoryList = List.of(
             new ActiveHearingDeviceItemFactory(),
             new AvailableHearingDeviceItemFactory(),
@@ -225,13 +228,17 @@
     public void onActiveDeviceChanged(@Nullable CachedBluetoothDevice activeDevice,
             int bluetoothProfile) {
         refreshDeviceUi();
-        if (mPresetController != null) {
-            mPresetController.setDevice(getActiveHearingDevice());
-            mMainHandler.post(() -> {
+        mMainHandler.post(() -> {
+            CachedBluetoothDevice device = getActiveHearingDevice();
+            if (mPresetController != null) {
+                mPresetController.setDevice(device);
                 mPresetLayout.setVisibility(
                         mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
-            });
-        }
+            }
+            if (mAmbientController != null) {
+                mAmbientController.loadDevice(device);
+            }
+        });
     }
 
     @Override
@@ -272,13 +279,13 @@
         }
 
         mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW, mLaunchSourceId);
-        mDeviceList = dialog.requireViewById(R.id.device_list);
-        mPresetLayout = dialog.requireViewById(R.id.preset_layout);
-        mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
 
         setupDeviceListView(dialog);
-        setupPresetSpinner(dialog);
         setupPairNewDeviceButton(dialog);
+        setupPresetSpinner(dialog);
+        if (com.android.settingslib.flags.Flags.hearingDevicesAmbientVolumeControl()) {
+            setupAmbientControls();
+        }
         if (com.android.systemui.Flags.hearingDevicesDialogRelatedTools()) {
             setupRelatedToolsView(dialog);
         }
@@ -286,41 +293,50 @@
 
     @Override
     public void onStart(@NonNull SystemUIDialog dialog) {
-        if (mLocalBluetoothManager == null) {
-            return;
-        }
-        mLocalBluetoothManager.getEventManager().registerCallback(this);
-        if (mPresetController != null) {
-            mPresetController.registerHapCallback();
-        }
+        ThreadUtils.postOnBackgroundThread(() -> {
+            if (mLocalBluetoothManager != null) {
+                mLocalBluetoothManager.getEventManager().registerCallback(this);
+            }
+            if (mPresetController != null) {
+                mPresetController.registerHapCallback();
+            }
+            if (mAmbientController != null) {
+                mAmbientController.start();
+            }
+        });
     }
 
     @Override
     public void onStop(@NonNull SystemUIDialog dialog) {
-        if (mLocalBluetoothManager == null) {
-            return;
-        }
-
-        if (mPresetController != null) {
-            mPresetController.unregisterHapCallback();
-        }
-        mLocalBluetoothManager.getEventManager().unregisterCallback(this);
+        ThreadUtils.postOnBackgroundThread(() -> {
+            if (mLocalBluetoothManager != null) {
+                mLocalBluetoothManager.getEventManager().unregisterCallback(this);
+            }
+            if (mPresetController != null) {
+                mPresetController.unregisterHapCallback();
+            }
+            if (mAmbientController != null) {
+                mAmbientController.stop();
+            }
+        });
     }
 
     private void setupDeviceListView(SystemUIDialog dialog) {
-        mDeviceList.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
+        final RecyclerView deviceList = dialog.requireViewById(R.id.device_list);
+        deviceList.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
         mHearingDeviceItemList = getHearingDeviceItemList();
         mDeviceListAdapter = new HearingDevicesListAdapter(mHearingDeviceItemList, this);
-        mDeviceList.setAdapter(mDeviceListAdapter);
+        deviceList.setAdapter(mDeviceListAdapter);
     }
 
     private void setupPresetSpinner(SystemUIDialog dialog) {
         mPresetController = new HearingDevicesPresetsController(mProfileManager, mPresetCallback);
         mPresetController.setDevice(getActiveHearingDevice());
 
+        mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
         mPresetInfoAdapter = new HearingDevicesSpinnerAdapter(dialog.getContext());
         mPresetSpinner.setAdapter(mPresetInfoAdapter);
-        // disable redundant Touch & Hold accessibility action for Switch Access
+        // Disable redundant Touch & Hold accessibility action for Switch Access
         mPresetSpinner.setAccessibilityDelegate(new View.AccessibilityDelegate() {
             @Override
             public void onInitializeAccessibilityNodeInfo(@NonNull View host,
@@ -349,12 +365,20 @@
             }
         });
 
+        mPresetLayout = dialog.requireViewById(R.id.preset_layout);
         mPresetLayout.setVisibility(mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
     }
 
+    private void setupAmbientControls() {
+        final AmbientVolumeLayout ambientLayout = mDialog.requireViewById(R.id.ambient_layout);
+        mAmbientController = new AmbientVolumeUiController(
+                mDialog.getContext(), mLocalBluetoothManager, ambientLayout);
+        mAmbientController.setShowUiWhenLocalDataExist(false);
+        mAmbientController.loadDevice(getActiveHearingDevice());
+    }
+
     private void setupPairNewDeviceButton(SystemUIDialog dialog) {
         final Button pairButton = dialog.requireViewById(R.id.pair_new_device_button);
-
         pairButton.setVisibility(mShowPairNewDevice ? VISIBLE : GONE);
         if (mShowPairNewDevice) {
             pairButton.setOnClickListener(v -> {
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 47910f3..11a6cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -114,9 +114,6 @@
             shadeBackActionInteractor.animateCollapseQs(false)
             return true
         }
-        if (shadeBackActionInteractor.closeUserSwitcherIfOpen()) {
-            return true
-        }
         if (glanceableHubBackAction()) {
             if (communalBackActionInteractor.canBeDismissed()) {
                 communalBackActionInteractor.onBackPressed()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModel.kt
index 6bafd14f..051cb41 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModel.kt
@@ -83,7 +83,7 @@
     }
 
     private suspend fun handleSetListener(appWidgetId: Int, listener: AppWidgetHostListener) =
-        withContextTraced("$TAG#setListenerInner", backgroundContext) {
+        withContextTraced("${TAG}_$appWidgetId#setListenerInner", backgroundContext) {
             if (
                 multiUserHelper.glanceableHubHsumFlagEnabled &&
                     multiUserHelper.isInHeadlessSystemUser()
@@ -92,13 +92,19 @@
                 // remotely in the foreground user, and therefore the host listener needs to be
                 // registered through the widget manager.
                 with(glanceableHubWidgetManagerLazy.get()) {
-                    setAppWidgetHostListener(appWidgetId, listenerDelegateFactory.create(listener))
+                    setAppWidgetHostListener(
+                        appWidgetId,
+                        listenerDelegateFactory.create("${TAG}_$appWidgetId", listener),
+                    )
                 }
             } else {
                 // Instead of setting the view as the listener directly, we wrap the view in a
                 // delegate which ensures the callbacks always get called on the main thread.
                 with(appWidgetHostLazy.get()) {
-                    setListener(appWidgetId, listenerDelegateFactory.create(listener))
+                    setListener(
+                        appWidgetId,
+                        listenerDelegateFactory.create("${TAG}_$appWidgetId", listener),
+                    )
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt
index 7d80acd..c0f7caa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt
@@ -19,11 +19,12 @@
 import android.appwidget.AppWidgetHost.AppWidgetHostListener
 import android.appwidget.AppWidgetProviderInfo
 import android.widget.RemoteViews
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.app.tracing.coroutines.launchTraced
+import com.android.systemui.dagger.qualifiers.Application
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import java.util.concurrent.Executor
+import kotlinx.coroutines.CoroutineScope
 
 /**
  * Wrapper for an [AppWidgetHostListener] to ensure the callbacks are executed on the main thread.
@@ -31,24 +32,27 @@
 class AppWidgetHostListenerDelegate
 @AssistedInject
 constructor(
-    @Main private val mainExecutor: Executor,
+    @Application private val mainScope: CoroutineScope,
+    @Assisted private val tag: String,
     @Assisted private val listener: AppWidgetHostListener,
 ) : AppWidgetHostListener {
 
     @AssistedFactory
     fun interface Factory {
-        fun create(listener: AppWidgetHostListener): AppWidgetHostListenerDelegate
+        fun create(tag: String, listener: AppWidgetHostListener): AppWidgetHostListenerDelegate
     }
 
     override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) {
-        mainExecutor.execute { listener.onUpdateProviderInfo(appWidget) }
+        mainScope.launchTraced("$tag#onUpdateProviderInfo") {
+            listener.onUpdateProviderInfo(appWidget)
+        }
     }
 
     override fun updateAppWidget(views: RemoteViews?) {
-        mainExecutor.execute { listener.updateAppWidget(views) }
+        mainScope.launchTraced("$tag#updateAppWidget") { listener.updateAppWidget(views) }
     }
 
     override fun onViewDataChanged(viewId: Int) {
-        mainExecutor.execute { listener.onViewDataChanged(viewId) }
+        mainScope.launchTraced("$tag#onViewDataChanged") { listener.onViewDataChanged(viewId) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/compose/ComposeModule.kt b/packages/SystemUI/src/com/android/systemui/compose/ComposeModule.kt
new file mode 100644
index 0000000..31b6f0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/compose/ComposeModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface ComposeModule {
+    @Binds
+    @IntoMap
+    @ClassKey(ComposeTracingStartable::class)
+    fun composeTracing(impl: ComposeTracingStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/ComposeTracingStartable.kt b/packages/SystemUI/src/com/android/systemui/compose/ComposeTracingStartable.kt
new file mode 100644
index 0000000..a015900
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/compose/ComposeTracingStartable.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(InternalComposeTracingApi::class)
+
+package com.android.systemui.compose
+
+import android.os.Trace
+import android.util.Log
+import androidx.compose.runtime.Composer
+import androidx.compose.runtime.CompositionTracer
+import androidx.compose.runtime.InternalComposeTracingApi
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.commandline.ParseableCommand
+import java.io.PrintWriter
+import javax.inject.Inject
+
+private const val TAG = "ComposeTracingStartable"
+private const val COMMAND_NAME = "composition-tracing"
+private const val SUBCOMMAND_ENABLE = "enable"
+private const val SUBCOMMAND_DISABLE = "disable"
+
+/**
+ * Sets up a [Command] to enable or disable Composition tracing.
+ *
+ * Usage:
+ * ```
+ * adb shell cmd statusbar composition-tracing [enable|disable]
+ * ${ANDROID_BUILD_TOP}/external/perfetto/tools/record_android_trace -c ${ANDROID_BUILD_TOP}/prebuilts/tools/linux-x86_64/perfetto/configs/trace_config_detailed.textproto
+ * ```
+ */
+@SysUISingleton
+class ComposeTracingStartable @Inject constructor(private val commandRegistry: CommandRegistry) :
+    CoreStartable {
+    @OptIn(InternalComposeTracingApi::class)
+    override fun start() {
+        Log.i(TAG, "Set up Compose tracing command")
+        commandRegistry.registerCommand(COMMAND_NAME) { CompositionTracingCommand() }
+    }
+}
+
+private class CompositionTracingCommand : ParseableCommand(COMMAND_NAME) {
+    val enable by subCommand(EnableCommand())
+    val disable by subCommand(DisableCommand())
+
+    override fun execute(pw: PrintWriter) {
+        if ((enable != null) xor (disable != null)) {
+            enable?.execute(pw)
+            disable?.execute(pw)
+        } else {
+            help(pw)
+        }
+    }
+}
+
+private class EnableCommand : ParseableCommand(SUBCOMMAND_ENABLE) {
+    override fun execute(pw: PrintWriter) {
+        val msg = "Enabled Composition tracing"
+        Log.i(TAG, msg)
+        pw.println(msg)
+        enableCompositionTracing()
+    }
+
+    private fun enableCompositionTracing() {
+        Composer.setTracer(
+            object : CompositionTracer {
+                override fun traceEventStart(key: Int, dirty1: Int, dirty2: Int, info: String) {
+                    Trace.traceBegin(Trace.TRACE_TAG_APP, info)
+                }
+
+                override fun traceEventEnd() = Trace.traceEnd(Trace.TRACE_TAG_APP)
+
+                override fun isTraceInProgress(): Boolean = Trace.isEnabled()
+            }
+        )
+    }
+}
+
+private class DisableCommand : ParseableCommand(SUBCOMMAND_DISABLE) {
+    override fun execute(pw: PrintWriter) {
+        val msg = "Disabled Composition tracing"
+        Log.i(TAG, msg)
+        pw.println(msg)
+        disableCompositionTracing()
+    }
+
+    private fun disableCompositionTracing() {
+        Composer.setTracer(null)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index d6f8957..7ebe52f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -54,6 +54,7 @@
 import com.android.systemui.common.usagestats.data.CommonUsageStatsDataLayerModule;
 import com.android.systemui.communal.dagger.CommunalModule;
 import com.android.systemui.complication.dagger.ComplicationComponent;
+import com.android.systemui.compose.ComposeModule;
 import com.android.systemui.controls.dagger.ControlsModule;
 import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -133,6 +134,7 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
@@ -141,7 +143,6 @@
 import com.android.systemui.statusbar.phone.ConfigurationControllerModule;
 import com.android.systemui.statusbar.phone.LetterboxModule;
 import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.PolicyModule;
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
@@ -214,6 +215,7 @@
         ClockRegistryModule.class,
         CommunalModule.class,
         CommonDataLayerModule.class,
+        ComposeModule.class,
         ConfigurationModule.class,
         ConfigurationRepositoryModule.class,
         CommonUsageStatsDataLayerModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index eaf5eac..73968da 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -126,24 +126,24 @@
 }
 
 class ServerFlagReaderFake : ServerFlagReader {
-    private val flagMap: MutableMap<String, Boolean> = mutableMapOf()
+    private val flagMap: MutableMap<Pair<String, String>, Boolean> = mutableMapOf()
     private val listeners =
         mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
 
     override fun hasOverride(namespace: String, name: String): Boolean {
-        return flagMap.containsKey(name)
+        return flagMap.containsKey(namespace to name)
     }
 
     override fun readServerOverride(namespace: String, name: String, default: Boolean): Boolean {
-        return flagMap.getOrDefault(name, default)
+        return flagMap.getOrDefault(namespace to name, default)
     }
 
     fun setFlagValue(namespace: String, name: String, value: Boolean) {
-        flagMap.put(name, value)
+        flagMap.put(namespace to name, value)
 
         for ((listener, flags) in listeners) {
             flagLoop@ for (flag in flags) {
-                if (name == flag.name) {
+                if (namespace == flag.namespace && name == flag.name) {
                     listener.onChange(flag, if (value) "true" else "false")
                     break@flagLoop
                 }
@@ -152,13 +152,13 @@
     }
 
     fun eraseFlag(namespace: String, name: String) {
-        flagMap.remove(name)
+        flagMap.remove(namespace to name)
     }
 
     override fun listenForChanges(
         flags: Collection<Flag<*>>,
         listener: ServerFlagReader.ChangeListener
     ) {
-        listeners.add(Pair(listener, flags))
+        listeners.add(listener to flags)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
index 62ab18b..9e89ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
+++ b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
@@ -17,26 +17,40 @@
 package com.android.systemui.grid.ui.compose
 
 import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.IntrinsicMeasurable
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ParentDataModifierNode
 import androidx.compose.ui.semantics.CollectionInfo
-import androidx.compose.ui.semantics.CollectionItemInfo
 import androidx.compose.ui.semantics.collectionInfo
-import androidx.compose.ui.semantics.collectionItemInfo
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastMap
+import androidx.compose.ui.util.fastMapIndexed
 import kotlin.math.max
 
+/** Creates a [SpannedGridState] that is remembered across recompositions. */
+@Composable
+fun rememberSpannedGridState(): SpannedGridState {
+    return remember { SpannedGridStateImpl() }
+}
+
 /**
- * Horizontal (non lazy) grid that supports [spans] for its elements.
+ * Horizontal (non lazy) grid that supports spans for its elements.
  *
  * The elements will be laid down vertically first, and then by columns. So assuming LTR layout, it
  * will be (for a span list `[2, 1, 2, 1, 1, 1, 1, 1]` and 4 rows):
@@ -50,8 +64,11 @@
  * where repeated numbers show larger span. If an element doesn't fit in a column due to its span,
  * it will start a new column.
  *
- * Elements in [spans] must be in the interval `[1, rows]` ([rows] > 0), and the composables are
- * associated with the corresponding span based on their index.
+ * Elements in [composables] can provide their span using [SpannedGridScope.span] and have a default
+ * span of 1. Spans must be in the interval `[1, columns]` ([columns] > 0).
+ *
+ * Passing a [SpannedGridState] can be useful to get access to the [SpannedGridState.positions],
+ * representing the row and column of each item.
  *
  * Due to the fact that elements are seen as a linear list that's laid out in a grid, the semantics
  * represent the collection as a list of elements.
@@ -61,23 +78,23 @@
     rows: Int,
     columnSpacing: Dp,
     rowSpacing: Dp,
-    spans: List<Int>,
     modifier: Modifier = Modifier,
-    composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
+    state: SpannedGridState = rememberSpannedGridState(),
+    composables: @Composable SpannedGridScope.() -> Unit,
 ) {
     SpannedGrid(
         primarySpaces = rows,
         crossAxisSpacing = rowSpacing,
         mainAxisSpacing = columnSpacing,
-        spans = spans,
         isVertical = false,
+        state = state,
         modifier = modifier,
         composables = composables,
     )
 }
 
 /**
- * Horizontal (non lazy) grid that supports [spans] for its elements.
+ * Horizontal (non lazy) grid that supports spans for its elements.
  *
  * The elements will be laid down horizontally first, and then by rows. So assuming LTR layout, it
  * will be (for a span list `[2, 1, 2, 1, 1, 1, 1, 1]` and 4 columns):
@@ -90,8 +107,11 @@
  * where repeated numbers show larger span. If an element doesn't fit in a row due to its span, it
  * will start a new row.
  *
- * Elements in [spans] must be in the interval `[1, columns]` ([columns] > 0), and the composables
- * are associated with the corresponding span based on their index.
+ * Elements in [composables] can provide their span using [SpannedGridScope.span] and have a default
+ * span of 1. Spans must be in the interval `[1, columns]` ([columns] > 0).
+ *
+ * Passing a [SpannedGridState] can be useful to get access to the [SpannedGridState.positions],
+ * representing the row and column of each item.
  *
  * Due to the fact that elements are seen as a linear list that's laid out in a grid, the semantics
  * represent the collection as a list of elements.
@@ -101,16 +121,16 @@
     columns: Int,
     columnSpacing: Dp,
     rowSpacing: Dp,
-    spans: List<Int>,
     modifier: Modifier = Modifier,
-    composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
+    state: SpannedGridState = rememberSpannedGridState(),
+    composables: @Composable SpannedGridScope.() -> Unit,
 ) {
     SpannedGrid(
         primarySpaces = columns,
         crossAxisSpacing = columnSpacing,
         mainAxisSpacing = rowSpacing,
-        spans = spans,
         isVertical = true,
+        state = state,
         modifier = modifier,
         composables = composables,
     )
@@ -121,18 +141,15 @@
     primarySpaces: Int,
     crossAxisSpacing: Dp,
     mainAxisSpacing: Dp,
-    spans: List<Int>,
     isVertical: Boolean,
+    state: SpannedGridState,
     modifier: Modifier = Modifier,
-    composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
+    composables: @Composable SpannedGridScope.() -> Unit,
 ) {
+    state as SpannedGridStateImpl
+    SideEffect { state.setOrientation(isVertical) }
+
     val crossAxisArrangement = Arrangement.spacedBy(crossAxisSpacing)
-    spans.forEachIndexed { index, span ->
-        check(span in 1..primarySpaces) {
-            "Span out of bounds. Span at index $index has value of $span which is outside of the " +
-                "expected rance of [1, $primarySpaces]"
-        }
-    }
 
     if (isVertical) {
         check(crossAxisSpacing >= 0.dp) { "Negative columnSpacing $crossAxisSpacing" }
@@ -142,21 +159,6 @@
         check(crossAxisSpacing >= 0.dp) { "Negative rowSpacing $crossAxisSpacing" }
     }
 
-    val totalMainAxisGroups: Int =
-        remember(primarySpaces, spans) {
-            var currentAccumulated = 0
-            var groups = 1
-            spans.forEach { span ->
-                if (currentAccumulated + span <= primarySpaces) {
-                    currentAccumulated += span
-                } else {
-                    groups += 1
-                    currentAccumulated = span
-                }
-            }
-            groups
-        }
-
     val slotPositionsAndSizesCache = remember {
         object {
             var sizes = IntArray(0)
@@ -165,25 +167,28 @@
     }
 
     Layout(
-        {
-            (0 until spans.size).map { spanIndex ->
-                Box(
-                    Modifier.semantics {
-                        collectionItemInfo =
-                            if (isVertical) {
-                                CollectionItemInfo(spanIndex, 1, 0, 1)
-                            } else {
-                                CollectionItemInfo(0, 1, spanIndex, 1)
-                            }
+        { SpannedGridScope.composables() },
+        modifier.semantics { collectionInfo = CollectionInfo(state.positions.size, 1) },
+    ) { measurables, constraints ->
+        val spans =
+            measurables.fastMapIndexed { index, measurable ->
+                measurable.spannedGridParentData.span.also { span ->
+                    check(span in 1..primarySpaces) {
+                        "Span out of bounds. Span at index $index has value of $span which is " +
+                            "outside of the expected rance of [1, $primarySpaces]"
                     }
-                ) {
-                    composables(spanIndex)
                 }
             }
-        },
-        modifier.semantics { collectionInfo = CollectionInfo(spans.size, 1) },
-    ) { measurables, constraints ->
-        check(measurables.size == spans.size)
+        var totalMainAxisGroups = 1
+        var currentAccumulated = 0
+        spans.forEach { span ->
+            if (currentAccumulated + span <= primarySpaces) {
+                currentAccumulated += span
+            } else {
+                totalMainAxisGroups += 1
+                currentAccumulated = span
+            }
+        }
         val crossAxisSize = if (isVertical) constraints.maxWidth else constraints.maxHeight
         check(crossAxisSize != Constraints.Infinity) { "Width must be constrained" }
         if (slotPositionsAndSizesCache.sizes.size != primarySpaces) {
@@ -275,11 +280,54 @@
                     }
                 placeable.placeRelative(x, y)
             }
+            state.onPlaceResults(placeables)
         }
     }
 }
 
-fun makeConstraint(isVertical: Boolean, mainAxisSize: Int, crossAxisSize: Int): Constraints {
+/** Receiver scope which is used by [VerticalSpannedGrid] and [HorizontalSpannedGrid] */
+@Stable
+object SpannedGridScope {
+    fun Modifier.span(span: Int) = this then SpanElement(span)
+}
+
+/** A state object that can be hoisted to observe items positioning */
+@Stable
+sealed interface SpannedGridState {
+    data class Position(val row: Int, val column: Int)
+
+    val positions: List<Position>
+}
+
+private class SpannedGridStateImpl : SpannedGridState {
+    private val _positions = mutableStateListOf<SpannedGridState.Position>()
+    override val positions
+        get() = _positions
+
+    private var isVertical by mutableStateOf(false)
+
+    fun onPlaceResults(placeResults: List<PlaceResult>) {
+        _positions.clear()
+        _positions.addAll(
+            placeResults.fastMap { placeResult ->
+                SpannedGridState.Position(
+                    row = if (isVertical) placeResult.mainAxisGroup else placeResult.slotIndex,
+                    column = if (isVertical) placeResult.slotIndex else placeResult.mainAxisGroup,
+                )
+            }
+        )
+    }
+
+    fun setOrientation(isVertical: Boolean) {
+        this.isVertical = isVertical
+    }
+}
+
+private fun makeConstraint(
+    isVertical: Boolean,
+    mainAxisSize: Int,
+    crossAxisSize: Int,
+): Constraints {
     return if (isVertical) {
         Constraints(maxHeight = mainAxisSize, minWidth = crossAxisSize, maxWidth = crossAxisSize)
     } else {
@@ -319,3 +367,27 @@
     val slotIndex: Int,
     val mainAxisGroup: Int,
 )
+
+private val IntrinsicMeasurable.spannedGridParentData: SpannedGridParentData?
+    get() = parentData as? SpannedGridParentData
+
+private val SpannedGridParentData?.span: Int
+    get() = this?.span ?: 1
+
+private data class SpannedGridParentData(val span: Int = 1)
+
+private data class SpanElement(val span: Int) : ModifierNodeElement<SpanNode>() {
+    override fun create(): SpanNode {
+        return SpanNode(span)
+    }
+
+    override fun update(node: SpanNode) {
+        node.span = span
+    }
+}
+
+private class SpanNode(var span: Int) : ParentDataModifierNode, Modifier.Node() {
+    override fun Density.modifyParentData(parentData: Any?): Any? {
+        return ((parentData as? SpannedGridParentData) ?: SpannedGridParentData()).copy(span = span)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
index 316964a..84c4bdf 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
@@ -19,6 +19,7 @@
 import android.service.quicksettings.Tile
 import com.android.systemui.Flags
 import com.android.systemui.animation.Expandable
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.util.kotlin.pairwise
@@ -173,6 +174,7 @@
     }
 }
 
+@SysUISingleton
 class TileHapticsViewModelFactoryProvider
 @Inject
 constructor(private val tileHapticsViewModelFactory: TileHapticsViewModel.Factory) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 4370abf..496c6fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -33,7 +33,6 @@
 import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
 import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
-import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.animation.ActivityTransitionAnimator;
@@ -108,8 +107,7 @@
 @Module(subcomponents = {
         KeyguardQsUserSwitchComponent.class,
         KeyguardStatusBarViewComponent.class,
-        KeyguardStatusViewComponent.class,
-        KeyguardUserSwitcherComponent.class},
+        KeyguardStatusViewComponent.class},
         includes = {
             DeviceEntryIconTransitionModule.class,
             FalsingModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
index 06da281..96b07cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
@@ -36,7 +36,7 @@
 import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
 
 /** Lockscreen affordance that opens the glanceable hub. */
 @SysUISingleton
@@ -60,13 +60,13 @@
         get() = R.drawable.ic_widgets
 
     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
-        get() = flow {
-            emit(
+        get() =
+            communalInteractor.isCommunalAvailable.map { available ->
                 if (!communalSettingsInteractor.isV2FlagEnabled()) {
                     Log.i(TAG, "Button hidden on lockscreen: flag not enabled.")
                     KeyguardQuickAffordanceConfig.LockScreenState.Hidden
-                } else if (!communalInteractor.isCommunalEnabled.value) {
-                    Log.i(TAG, "Button hidden on lockscreen: hub not enabled in settings.")
+                } else if (!available) {
+                    Log.i(TAG, "Button hidden on lockscreen: hub not available.")
                     KeyguardQuickAffordanceConfig.LockScreenState.Hidden
                 } else {
                     KeyguardQuickAffordanceConfig.LockScreenState.Visible(
@@ -77,8 +77,7 @@
                             )
                     )
                 }
-            )
-        }
+            }
 
     override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
         return if (!communalSettingsInteractor.isV2FlagEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
index f08576a..c774987 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
@@ -24,8 +24,8 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
@@ -51,6 +51,7 @@
     @ShadeDisplayAware private val context: Context,
     private val userFileManager: UserFileManager,
     private val userTracker: UserTracker,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
     broadcastDispatcher: BroadcastDispatcher,
 ) : KeyguardQuickAffordanceSelectionManager {
 
@@ -102,7 +103,7 @@
                     // common case anyway as restoration really only happens on initial device
                     // setup).
                     emit(Unit)
-                }
+                },
             ) { _, _ ->
             }
             .flatMapLatest {
@@ -128,7 +129,11 @@
 
     override fun getSelections(): Map<String, List<String>> {
         // If the custom shortcuts feature is not enabled, ignore prior selections and use defaults
-        if (!context.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)) {
+        // TODO(b/383391342): remove isV2FlagEnabled check and just depend on the resource
+        if (
+            !(context.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled) ||
+                communalSettingsInteractor.isV2FlagEnabled())
+        ) {
             return defaults
         }
 
@@ -164,10 +169,7 @@
         return result
     }
 
-    override fun setSelections(
-        slotId: String,
-        affordanceIds: List<String>,
-    ) {
+    override fun setSelections(slotId: String, affordanceIds: List<String>) {
         val key = "$KEY_PREFIX_SLOT$slotId"
         val value = affordanceIds.joinToString(AFFORDANCE_DELIMITER)
         sharedPrefs.edit().putString(key, value).apply()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 9c2daf5..7d8badd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -28,6 +28,7 @@
 import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
@@ -90,6 +91,7 @@
     private val devicePolicyManager: DevicePolicyManager,
     private val dockManager: DockManager,
     private val biometricSettingsRepository: BiometricSettingsRepository,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     @ShadeDisplayAware private val appContext: Context,
     private val sceneInteractor: Lazy<SceneInteractor>,
@@ -462,7 +464,10 @@
                 name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED,
                 value =
                     !isFeatureDisabledByDevicePolicy() &&
-                        appContext.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled),
+                        // TODO(b/383391342): remove isV2FlagEnabled check once trunkfood is reached
+                        (appContext.resources.getBoolean(
+                            R.bool.custom_lockscreen_shortcuts_enabled
+                        ) || communalSettingsInteractor.isV2FlagEnabled()),
             ),
             KeyguardPickerFlag(
                 name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
index aa44b6d..382436c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor.scenetransition
 
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.CoreStartable
@@ -38,7 +39,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * This class listens to scene framework scene transitions and manages keyguard transition framework
@@ -111,7 +111,10 @@
         if (currentTransitionId == null) return
         if (prevTransition !is ObservableTransitionState.Transition) return
 
-        if (idle.currentScene == prevTransition.toContent) {
+        if (
+            idle.currentScene == prevTransition.toContent ||
+                idle.currentOverlays.contains(prevTransition.toContent)
+        ) {
             finishCurrentTransition()
         } else {
             val targetState =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
index b9994d7..4b30645 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.panels.ui.compose
 
 import com.android.compose.animation.Bounceable
+import com.android.systemui.grid.ui.compose.SpannedGridState
 import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.ui.model.GridCell
 import com.android.systemui.qs.panels.ui.model.TileGridCell
@@ -37,22 +38,37 @@
     val cell = this[index].first as TileGridCell
     // Only look for neighbor bounceables if they are on the same row
     val onLastColumn = cell.onLastColumn(cell.column, columns)
-    val previousTile = getOrNull(index - 1)?.takeIf { cell.column != 0 }
-    val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn }
+    val previousTile = getOrNull(index - 1)?.takeIf { it.first.row == cell.row }
+    val nextTile = getOrNull(index + 1)?.takeIf { it.first.row == cell.row }
     return BounceableInfo(this[index].second, previousTile?.second, nextTile?.second, !onLastColumn)
 }
 
-fun List<BounceableTileViewModel>.bounceableInfo(
-    sizedTile: SizedTile<TileViewModel>,
-    index: Int,
-    column: Int,
+inline fun List<SizedTile<TileViewModel>>.forEachWithBounceables(
+    positions: List<SpannedGridState.Position>,
+    bounceables: List<BounceableTileViewModel>,
     columns: Int,
-): BounceableInfo {
-    // Only look for neighbor bounceables if they are on the same row
-    val onLastColumn = sizedTile.onLastColumn(column, columns)
-    val previousTile = getOrNull(index - 1)?.takeIf { column != 0 }
-    val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn }
-    return BounceableInfo(this[index], previousTile, nextTile, !onLastColumn)
+    action: (index: Int, tile: SizedTile<TileViewModel>, bounceableInfo: BounceableInfo) -> Unit,
+) {
+    this.forEachIndexed { index, tile ->
+        val position = positions.getOrNull(index)
+        val onLastColumn = position?.column == columns - tile.width
+        val previousBounceable =
+            bounceables.getOrNull(index - 1)?.takeIf {
+                position != null && positions.getOrNull(index - 1)?.row == position.row
+            }
+        val nextBounceable =
+            bounceables.getOrNull(index + 1)?.takeIf {
+                position != null && positions.getOrNull(index + 1)?.row == position.row
+            }
+        val bounceableInfo =
+            BounceableInfo(
+                bounceable = bounceables[index],
+                previousTile = previousBounceable,
+                nextTile = nextBounceable,
+                bounceEnd = !onLastColumn,
+            )
+        action(index, tile, bounceableInfo)
+    }
 }
 
 private fun <T> SizedTile<T>.onLastColumn(column: Int, columns: Int): Boolean {
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 2928ad1..ceff94a 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
@@ -29,6 +29,7 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.grid.ui.compose.VerticalSpannedGrid
+import com.android.systemui.grid.ui.compose.rememberSpannedGridState
 import com.android.systemui.qs.composefragment.ui.GridAnchor
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.Tile
 import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel
@@ -47,6 +48,8 @@
     val bounceables = remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } }
     val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
     val scope = rememberCoroutineScope()
+    val gridState = rememberSpannedGridState()
+    val positions = gridState.positions
 
     DisposableEffect(tiles) {
         val token = Any()
@@ -54,30 +57,34 @@
         onDispose { tiles.forEach { it.stopListening(token) } }
     }
     val columns = viewModel.columns
-    var cellIndex = 0
     Box(modifier = modifier) {
         GridAnchor()
         VerticalSpannedGrid(
             columns = columns,
             columnSpacing = dimensionResource(R.dimen.qs_tile_margin_horizontal),
             rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical),
-            spans = sizedTiles.fastMap { it.width },
+            state = gridState,
             modifier = Modifier.sysuiResTag("qqs_tile_layout"),
-        ) { spanIndex ->
-            val it = sizedTiles[spanIndex]
-            val column = cellIndex % columns
-            cellIndex += it.width
-            Tile(
-                tile = it.tile,
-                iconOnly = it.isIcon,
-                modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
-                squishiness = { squishiness },
-                coroutineScope = scope,
-                bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
-                tileHapticsViewModelFactoryProvider = viewModel.tileHapticsViewModelFactoryProvider,
-                // There should be no QuickQuickSettings when the details view is enabled.
-                detailsViewModel = null,
-            )
+        ) {
+            sizedTiles.forEachWithBounceables(positions, bounceables, columns) {
+                index,
+                sizedTile,
+                bounceableInfo ->
+                Tile(
+                    tile = sizedTile.tile,
+                    iconOnly = sizedTile.isIcon,
+                    modifier =
+                        Modifier.element(sizedTile.tile.spec.toElementKey(index))
+                            .span(sizedTile.width),
+                    squishiness = { squishiness },
+                    coroutineScope = scope,
+                    bounceableInfo = bounceableInfo,
+                    tileHapticsViewModelFactoryProvider =
+                        viewModel.tileHapticsViewModelFactoryProvider,
+                    // There should be no QuickQuickSettings when the details view is enabled.
+                    detailsViewModel = null,
+                )
+            }
         }
     }
 }
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 d975f10..60c78a0 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
@@ -395,7 +395,7 @@
 
     val cells =
         remember(listState.tiles) {
-            listState.tiles.fastMap { Pair(it, BounceableTileViewModel()) }
+            listState.tiles.fastMap { tile -> Pair(tile, BounceableTileViewModel()) }
         }
 
     TileLazyGrid(
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 8fd99a5..8a12889 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
@@ -23,17 +23,18 @@
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.util.fastMap
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.grid.ui.compose.VerticalSpannedGrid
+import com.android.systemui.grid.ui.compose.rememberSpannedGridState
 import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
 import com.android.systemui.qs.panels.ui.compose.bounceableInfo
+import com.android.systemui.qs.panels.ui.compose.forEachWithBounceables
 import com.android.systemui.qs.panels.ui.compose.rememberEditListState
 import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
@@ -83,27 +84,32 @@
             remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } }
         val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
         val scope = rememberCoroutineScope()
-        var cellIndex = 0
+        val gridState = rememberSpannedGridState()
+        val positions = gridState.positions
 
         VerticalSpannedGrid(
             columns = columns,
+            state = gridState,
             columnSpacing = dimensionResource(R.dimen.qs_tile_margin_horizontal),
             rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical),
-            spans = sizedTiles.fastMap { it.width },
-        ) { spanIndex ->
-            val it = sizedTiles[spanIndex]
-            val column = cellIndex % columns
-            cellIndex += it.width
-            Tile(
-                tile = it.tile,
-                iconOnly = iconTilesViewModel.isIconTile(it.tile.spec),
-                modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
-                squishiness = { squishiness },
-                tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider,
-                coroutineScope = scope,
-                bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
-                detailsViewModel = detailsViewModel,
-            )
+        ) {
+            sizedTiles.forEachWithBounceables(positions, bounceables, columns) {
+                index,
+                sizedTile,
+                bounceableInfo ->
+                Tile(
+                    tile = sizedTile.tile,
+                    iconOnly = iconTilesViewModel.isIconTile(sizedTile.tile.spec),
+                    modifier =
+                        Modifier.element(sizedTile.tile.spec.toElementKey(index))
+                            .span(sizedTile.width),
+                    squishiness = { squishiness },
+                    tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider,
+                    coroutineScope = scope,
+                    bounceableInfo = bounceableInfo,
+                    detailsViewModel = detailsViewModel,
+                )
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
index 989fc0f..5ba1527 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
@@ -22,6 +22,7 @@
 import android.service.quicksettings.Tile
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
@@ -92,7 +93,8 @@
 
         state?.apply {
             this.state = tileState.activationState.legacyState
-            icon = maybeLoadResourceIcon(tileState.iconRes ?: R.drawable.ic_qs_notes)
+            icon =
+                maybeLoadResourceIcon((tileState.icon as Icon.Loaded).res ?: R.drawable.ic_qs_notes)
             label = tileState.label
             contentDescription = tileState.contentDescription
             expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
index 34c2ec9..80d429c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -35,13 +35,13 @@
 
     override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            iconRes =
+            val iconRes =
                 if (data.isEnabled) {
                     R.drawable.qs_airplane_icon_on
                 } else {
                     R.drawable.qs_airplane_icon_off
                 }
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index a72992d..d56d994 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -84,8 +84,8 @@
                     secondaryLabel = resources.getString(R.string.qs_alarm_tile_no_alarm)
                 }
             }
-            iconRes = R.drawable.ic_alarm
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            val iconRes = R.drawable.ic_alarm
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             sideViewIcon = QSTileState.SideViewIcon.Chevron
             contentDescription = label
             supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
index e116d8c..72759c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -38,10 +38,10 @@
         QSTileState.build(resources, theme, config.uiConfig) {
             label = resources.getString(R.string.battery_detail_switch_title)
             contentDescription = label
-            iconRes =
+            val iconRes =
                 if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
                 else R.drawable.qs_battery_saver_icon_off
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             sideViewIcon = QSTileState.SideViewIcon.None
 
             if (data.isPluggedIn) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
index 21b9f65..e5a0fe8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
@@ -37,8 +37,8 @@
     override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction)
-            iconRes = R.drawable.ic_qs_color_correction
-            icon = Icon.Loaded(resources.getDrawable(R.drawable.ic_qs_color_correction)!!, null)
+            val iconRes = R.drawable.ic_qs_color_correction
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index 2dfb1fc..32ccba6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -35,14 +35,14 @@
 
     override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            iconRes =
+            val iconRes =
                 if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
                     R.drawable.qs_flashlight_icon_on
                 } else {
                     R.drawable.qs_flashlight_icon_off
                 }
 
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
 
             contentDescription = label
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
index 7f41cbd..c571b13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -36,8 +36,8 @@
 
     override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            iconRes = R.drawable.ic_qs_font_scaling
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            val iconRes = R.drawable.ic_qs_font_scaling
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             contentDescription = label
             activationState = QSTileState.ActivationState.ACTIVE
             sideViewIcon = QSTileState.SideViewIcon.Chevron
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
index 4c302b3..12f7149 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
@@ -37,8 +37,8 @@
     override fun map(config: QSTileConfig, data: HearingDevicesTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             label = resources.getString(R.string.quick_settings_hearing_devices_label)
-            iconRes = R.drawable.qs_hearing_devices_icon
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            val iconRes = R.drawable.qs_hearing_devices_icon
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             sideViewIcon = QSTileState.SideViewIcon.Chevron
             contentDescription = label
             if (data.isAnyActiveHearingDevice) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
index 1a6876d..7ad01e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -61,11 +61,11 @@
 
             when (val dataIcon = data.icon) {
                 is InternetTileIconModel.ResourceId -> {
-                    iconRes = dataIcon.resId
                     icon =
                         Icon.Loaded(
                             resources.getDrawable(dataIcon.resId, theme),
                             contentDescription = null,
+                            dataIcon.resId,
                         )
                 }
 
@@ -76,11 +76,11 @@
                 }
 
                 is InternetTileIconModel.Satellite -> {
-                    iconRes = dataIcon.resourceIcon.res // level is inferred from res
                     icon =
                         Icon.Loaded(
                             resources.getDrawable(dataIcon.resourceIcon.res, theme),
                             contentDescription = null,
+                            dataIcon.resourceIcon.res,
                         )
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
index 8d35b24..05590e8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
@@ -35,7 +35,7 @@
     override fun map(config: QSTileConfig, data: ColorInversionTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             val subtitleArray = resources.getStringArray(R.array.tile_states_inversion)
-
+            val iconRes: Int
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = subtitleArray[2]
@@ -45,7 +45,7 @@
                 secondaryLabel = subtitleArray[1]
                 iconRes = R.drawable.qs_invert_colors_icon_off
             }
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             contentDescription = label
             supportedActions =
                 setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
index 3557c1a..afb137e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
@@ -39,6 +39,7 @@
                     Icon.Loaded(
                         resources.getDrawable(R.drawable.qs_record_issue_icon_on, theme),
                         null,
+                        R.drawable.qs_record_issue_icon_on,
                     )
                 } else {
                     activationState = QSTileState.ActivationState.INACTIVE
@@ -46,6 +47,7 @@
                     Icon.Loaded(
                         resources.getDrawable(R.drawable.qs_record_issue_icon_off, theme),
                         null,
+                        R.drawable.qs_record_issue_icon_off,
                     )
                 }
             supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
index dfc24a1..ced5a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -35,13 +35,13 @@
 
     override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            iconRes =
+            val iconRes =
                 if (data.isEnabled) {
                     R.drawable.qs_location_icon_on
                 } else {
                     R.drawable.qs_location_icon_off
                 }
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
 
             label = resources.getString(R.string.quick_settings_location_label)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index ee6b0b8..479f618 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.qs.tiles.impl.modes.domain.interactor
 
 import android.content.Context
+import android.graphics.drawable.Drawable
 import android.os.UserHandle
 import com.android.app.tracing.coroutines.flow.flowName
 import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.asIcon
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.modes.shared.ModesUi
 import com.android.systemui.modes.shared.ModesUiIcons
@@ -31,7 +31,6 @@
 import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
-import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -68,46 +67,29 @@
     suspend fun getCurrentTileModel() = buildTileData(zenModeInteractor.getActiveModes())
 
     private fun buildTileData(activeModes: ActiveZenModes): ModesTileModel {
-        if (ModesUiIcons.isEnabled) {
-            val tileIcon = getTileIcon(activeModes.mainMode)
-            return ModesTileModel(
-                isActivated = activeModes.isAnyActive(),
-                icon = tileIcon.icon,
-                iconResId = tileIcon.resId,
-                activeModes = activeModes.modeNames,
-            )
-        } else {
-            return ModesTileModel(
-                isActivated = activeModes.isAnyActive(),
-                icon =
-                    context
-                        .getDrawable(ModesTile.ICON_RES_ID)!!
-                        .asIcon(res = ModesTile.ICON_RES_ID),
-                iconResId = ModesTile.ICON_RES_ID,
-                activeModes = activeModes.modeNames,
-            )
-        }
-    }
+        val drawable: Drawable
+        val iconRes: Int?
+        val activeMode = activeModes.mainMode
 
-    private data class TileIcon(val icon: Icon.Loaded, val resId: Int?)
-
-    private fun getTileIcon(activeMode: ZenModeInfo?): TileIcon {
-        return if (activeMode != null) {
+        if (ModesUiIcons.isEnabled && activeMode != null) {
             // ZenIconKey.resPackage is null if its resId is a system icon.
-            if (activeMode.icon.key.resPackage == null) {
-                TileIcon(
-                    activeMode.icon.drawable.asIcon(res = activeMode.icon.key.resId),
-                    activeMode.icon.key.resId,
-                )
-            } else {
-                TileIcon(activeMode.icon.drawable.asIcon(), null)
-            }
+            iconRes =
+                if (activeMode.icon.key.resPackage == null) {
+                    activeMode.icon.key.resId
+                } else {
+                    null
+                }
+            drawable = activeMode.icon.drawable
         } else {
-            TileIcon(
-                context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(res = ModesTile.ICON_RES_ID),
-                ModesTile.ICON_RES_ID,
-            )
+            iconRes = ModesTile.ICON_RES_ID
+            drawable = context.getDrawable(iconRes)!!
         }
+
+        return ModesTileModel(
+            isActivated = activeModes.isAnyActive(),
+            icon = Icon.Loaded(drawable, null, iconRes),
+            activeModes = activeModes.modeNames,
+        )
     }
 
     override fun availability(user: UserHandle): Flow<Boolean> = flowOf(ModesUi.isEnabled)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
index db48123..d0eacbc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
@@ -21,12 +21,10 @@
 data class ModesTileModel(
     val isActivated: Boolean,
     val activeModes: List<String>,
-    val icon: Icon.Loaded,
-
     /**
-     * Resource id corresponding to [icon]. Will only be present if it's know to correspond to a
-     * resource with a known id in SystemUI (such as resources from `android.R`,
-     * `com.android.internal.R`, or `com.android.systemui.res` itself).
+     * icon.res will only be present if it is known to correspond to a resource with a known id in
+     * SystemUI (such as resources from `android.R`, `com.android.internal.R`, or
+     * `com.android.systemui.res` itself).
      */
-    val iconResId: Int? = null
+    val icon: Icon.Loaded,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 1507ef4..99ae3b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -34,7 +34,6 @@
     QSTileDataToStateMapper<ModesTileModel> {
     override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            iconRes = data.iconResId
             icon = data.icon
             activationState =
                 if (data.isActivated) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
index 3569e4d..16b3628 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -49,7 +49,7 @@
             supportedActions =
                 setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
             sideViewIcon = QSTileState.SideViewIcon.None
-
+            val iconRes: Int
             if (data.isActivated) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 iconRes = R.drawable.qs_nightlight_icon_on
@@ -58,7 +58,7 @@
                 iconRes = R.drawable.qs_nightlight_icon_off
             }
 
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
 
             secondaryLabel = getSecondaryLabel(data, resources)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
index a543619..ecdd711 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
@@ -35,8 +35,8 @@
 ) : QSTileDataToStateMapper<NotesTileModel> {
     override fun map(config: QSTileConfig, data: NotesTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            iconRes = R.drawable.ic_qs_notes
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
+            val iconRes = R.drawable.ic_qs_notes
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             contentDescription = label
             activationState = QSTileState.ActivationState.INACTIVE
             sideViewIcon = QSTileState.SideViewIcon.Chevron
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
index 76f1e8b..5b3ea93 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
@@ -38,8 +38,8 @@
         QSTileState.build(resources, theme, config.uiConfig) {
             val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded)
             label = resources.getString(R.string.quick_settings_onehanded_label)
-            iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            val iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
index c546250..21e92d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -38,8 +38,8 @@
         QSTileState.build(resources, theme, config.uiConfig) {
             label = resources.getString(R.string.qr_code_scanner_title)
             contentDescription = label
-            iconRes = R.drawable.ic_qr_code_scanner
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            val iconRes = R.drawable.ic_qr_code_scanner
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             sideViewIcon = QSTileState.SideViewIcon.Chevron
             supportedActions = setOf(QSTileState.UserAction.CLICK)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
index 66d0f96..66759cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
@@ -37,6 +37,7 @@
 
     override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
+            val iconRes: Int
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 iconRes = R.drawable.qs_extra_dim_icon_on
@@ -50,7 +51,7 @@
                     resources
                         .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE]
             }
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             label =
                 resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
             contentDescription = label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
index a014422..000c702 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
@@ -42,7 +42,7 @@
         QSTileState.build(resources, theme, config.uiConfig) {
             label = resources.getString(R.string.quick_settings_rotation_unlocked_label)
             contentDescription = resources.getString(R.string.accessibility_quick_settings_rotation)
-
+            val iconRes: Int
             if (data.isRotationLocked) {
                 activationState = QSTileState.ActivationState.INACTIVE
                 secondaryLabel = EMPTY_SECONDARY_STRING
@@ -57,7 +57,7 @@
                     }
                 iconRes = R.drawable.qs_auto_rotate_icon_on
             }
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             if (isDeviceFoldable(resources, deviceStateManager)) {
                 secondaryLabel = getSecondaryLabelWithPosture(activationState)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
index aea4967..1d5cf29 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -36,6 +36,7 @@
     override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             with(data) {
+                val iconRes: Int
                 if (isEnabled) {
                     activationState = QSTileState.ActivationState.ACTIVE
                     iconRes = R.drawable.qs_data_saver_icon_on
@@ -45,7 +46,7 @@
                     iconRes = R.drawable.qs_data_saver_icon_off
                     secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1]
                 }
-                icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+                icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
                 contentDescription = label
                 supportedActions =
                     setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
index f3136e0..0a61e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
@@ -38,7 +38,7 @@
         QSTileState.build(resources, theme, config.uiConfig) {
             label = resources.getString(R.string.quick_settings_screen_record_label)
             supportedActions = setOf(QSTileState.UserAction.CLICK)
-
+            val iconRes: Int
             when (data) {
                 is ScreenRecordModel.Recording -> {
                     activationState = QSTileState.ActivationState.ACTIVE
@@ -61,7 +61,7 @@
                         resources.getString(R.string.quick_settings_screen_record_start)
                 }
             }
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
 
             contentDescription =
                 if (TextUtils.isEmpty(secondaryLabel)) label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
index 73e61b7..f54f46c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
@@ -50,8 +50,8 @@
             contentDescription = label
             supportedActions =
                 setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
-            iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked)
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+            val iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked)
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
             sideViewIcon = QSTileState.SideViewIcon.None
 
             if (data.isBlocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
index e9aa46c..5933d65 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
@@ -116,11 +116,11 @@
                     }
                 }
 
-                iconRes =
+                val iconRes =
                     if (activationState == QSTileState.ActivationState.ACTIVE)
                         R.drawable.qs_light_dark_theme_icon_on
                     else R.drawable.qs_light_dark_theme_icon_off
-                icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+                icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
 
                 supportedActions =
                     if (activationState == QSTileState.ActivationState.UNAVAILABLE)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
index 6a3195a..5b462ba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
@@ -41,8 +41,8 @@
         QSTileState.build(resources, theme, config.uiConfig) {
             label = getTileLabel()!!
             contentDescription = label
-            iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
-            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
+            val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
+            icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
 
             when (data) {
                 is WorkModeTileModel.HasActiveProfile -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 8394be5..c6af729 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -36,7 +36,6 @@
  */
 data class QSTileState(
     val icon: Icon?,
-    val iconRes: Int?,
     val label: CharSequence,
     val activationState: ActivationState,
     val secondaryLabel: CharSequence?,
@@ -58,7 +57,7 @@
         ): QSTileState {
             val iconDrawable = resources.getDrawable(config.iconRes, theme)
             return build(
-                Icon.Loaded(iconDrawable, null),
+                Icon.Loaded(iconDrawable, null, config.iconRes),
                 resources.getString(config.labelRes),
                 builder,
             )
@@ -115,7 +114,6 @@
     }
 
     class Builder(var icon: Icon?, var label: CharSequence) {
-        var iconRes: Int? = null
         var activationState: ActivationState = ActivationState.INACTIVE
         var secondaryLabel: CharSequence? = null
         var supportedActions: Set<UserAction> = setOf(UserAction.CLICK)
@@ -128,7 +126,6 @@
         fun build(): QSTileState =
             QSTileState(
                 icon,
-                iconRes,
                 label,
                 activationState,
                 secondaryLabel,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index 632eeef..c34edc8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -260,8 +260,8 @@
                 icon =
                     when (val stateIcon = viewModelState.icon) {
                         is Icon.Loaded ->
-                            if (viewModelState.iconRes == null) DrawableIcon(stateIcon.drawable)
-                            else DrawableIconWithRes(stateIcon.drawable, viewModelState.iconRes)
+                            if (stateIcon.res == null) DrawableIcon(stateIcon.drawable)
+                            else DrawableIconWithRes(stateIcon.drawable, stateIcon.res)
                         is Icon.Resource -> ResourceIcon.get(stateIcon.res)
                         null -> null
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 9e88583..28c675e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -58,7 +58,6 @@
 import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -69,7 +68,6 @@
 import android.os.Handler;
 import android.os.Trace;
 import android.os.UserManager;
-import android.provider.Settings;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.MathUtils;
@@ -106,10 +104,8 @@
 import com.android.keyguard.KeyguardStatusViewController;
 import com.android.keyguard.KeyguardUnfoldTransition;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
 import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
-import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Gefingerpoken;
@@ -221,10 +217,7 @@
 import com.android.systemui.statusbar.phone.TapAgainViewController;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.util.Compile;
@@ -309,7 +302,6 @@
     private final ShadeHeadsUpChangedListener mOnHeadsUpChangedListener =
             new ShadeHeadsUpChangedListener();
     private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
-    private final SettingsChangeObserver mSettingsChangeObserver;
     private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener();
     private final NotificationPanelView mView;
     private final VibratorHelper mVibratorHelper;
@@ -331,8 +323,6 @@
     private final MediaHierarchyManager mMediaHierarchyManager;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-    private final KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
-    private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
     private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
     private final FragmentService mFragmentService;
     private final IStatusBarService mStatusBarService;
@@ -381,8 +371,6 @@
     private float mKeyguardNotificationTopPadding;
     /** Current max allowed keyguard notifications determined by measuring the panel. */
     private int mMaxAllowedKeyguardNotifications;
-    private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
-    private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
     private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
     private KeyguardStatusViewController mKeyguardStatusViewController;
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
@@ -394,8 +382,6 @@
     private OpenCloseListener mOpenCloseListener;
     private GestureRecorder mGestureRecorder;
 
-    private boolean mKeyguardQsUserSwitchEnabled;
-    private boolean mKeyguardUserSwitcherEnabled;
     private boolean mDozing;
     private boolean mDozingOnDown;
     private boolean mBouncerShowing;
@@ -691,8 +677,6 @@
             NotificationsQSContainerController notificationsQSContainerController,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
-            KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory,
-            KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
             KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
             AuthController authController,
@@ -841,11 +825,8 @@
         mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
         mDepthController = notificationShadeDepthController;
         mContentResolver = contentResolver;
-        mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
-        mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
         mFragmentService = fragmentService;
         mStatusBarService = statusBarService;
-        mSettingsChangeObserver = new SettingsChangeObserver(handler);
         mSplitShadeStateController = splitShadeStateController;
         mSplitShadeEnabled =
                 mSplitShadeStateController.shouldUseSplitNotificationShade(mResources);
@@ -874,7 +855,6 @@
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         dynamicPrivacyController.addListener(this::onDynamicPrivacyChanged);
         quickSettingsController.setExpansionHeightListener(this::onQsSetExpansionHeightCalled);
-        quickSettingsController.setQsStateUpdateListener(this::onQsStateUpdated);
         quickSettingsController.setApplyClippingImmediatelyListener(
                 this::onQsClippingImmediatelyApplied);
         quickSettingsController.setFlingQsWithoutClickListener(this::onFlingQsWithoutClick);
@@ -917,7 +897,6 @@
         mKeyguardUnfoldTransition = unfoldComponent.map(
                 SysUIUnfoldComponent::getKeyguardUnfoldTransition);
 
-        updateUserSwitcherFlags();
         mKeyguardClockInteractor = keyguardClockInteractor;
         KeyguardLongPressViewBinder.bind(
                 mView.requireViewById(R.id.keyguard_long_press),
@@ -1009,30 +988,14 @@
     void onFinishInflate() {
         loadDimens();
 
-        FrameLayout userAvatarContainer = null;
-        KeyguardUserSwitcherView keyguardUserSwitcherView = null;
-
-        if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled(
-                mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user))) {
-            if (mKeyguardQsUserSwitchEnabled) {
-                ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub);
-                userAvatarContainer = (FrameLayout) stub.inflate();
-            } else {
-                ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub);
-                keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate();
-            }
-        }
-
         mKeyguardStatusBarViewController =
                 mKeyguardStatusBarViewComponentFactory.build(
                                 mView.findViewById(R.id.keyguard_header),
                                 mShadeViewStateProvider)
                         .getKeyguardStatusBarViewController();
         mKeyguardStatusBarViewController.init();
-
         mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
-        updateViewControllers(userAvatarContainer, keyguardUserSwitcherView);
-
+        updateStatusViewController();
         mNotificationStackScrollLayoutController.setOnHeightChangedListener(
                 new NsslHeightChangedListener());
         mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
@@ -1225,39 +1188,6 @@
         }
     }
 
-    private void updateViewControllers(
-            FrameLayout userAvatarView,
-            KeyguardUserSwitcherView keyguardUserSwitcherView) {
-        updateStatusViewController();
-        if (mKeyguardUserSwitcherController != null) {
-            // Try to close the switcher so that callbacks are triggered if necessary.
-            // Otherwise, NPV can get into a state where some of the views are still hidden
-            mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(false);
-        }
-
-        mKeyguardQsUserSwitchController = null;
-        mKeyguardUserSwitcherController = null;
-
-        // Re-associate the KeyguardUserSwitcherController
-        if (userAvatarView != null) {
-            KeyguardQsUserSwitchComponent userSwitcherComponent =
-                    mKeyguardQsUserSwitchComponentFactory.build(userAvatarView);
-            mKeyguardQsUserSwitchController =
-                    userSwitcherComponent.getKeyguardQsUserSwitchController();
-            mKeyguardQsUserSwitchController.init();
-            mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true);
-        } else if (keyguardUserSwitcherView != null) {
-            KeyguardUserSwitcherComponent userSwitcherComponent =
-                    mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView);
-            mKeyguardUserSwitcherController =
-                    userSwitcherComponent.getKeyguardUserSwitcherController();
-            mKeyguardUserSwitcherController.init();
-            mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true);
-        } else {
-            mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(false);
-        }
-    }
-
     /** Updates the StatusBarViewController and updates any that depend on it. */
     public void updateStatusViewController() {
         // Re-associate the KeyguardStatusViewController
@@ -1391,29 +1321,7 @@
 
         // we need to update KeyguardStatusView constraints after reinflating it
         updateResources();
-
-        // Re-inflate the keyguard user switcher group.
-        updateUserSwitcherFlags();
-        boolean isUserSwitcherEnabled = mUserManager.isUserSwitcherEnabled(
-                mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user));
-        boolean showQsUserSwitch = mKeyguardQsUserSwitchEnabled && isUserSwitcherEnabled;
-        boolean showKeyguardUserSwitcher =
-                !mKeyguardQsUserSwitchEnabled
-                        && mKeyguardUserSwitcherEnabled
-                        && isUserSwitcherEnabled;
-        FrameLayout userAvatarView = (FrameLayout) reInflateStub(
-                R.id.keyguard_qs_user_switch_view /* viewId */,
-                R.id.keyguard_qs_user_switch_stub /* stubId */,
-                R.layout.keyguard_qs_user_switch /* layoutId */,
-                showQsUserSwitch /* enabled */);
-        KeyguardUserSwitcherView keyguardUserSwitcherView =
-                (KeyguardUserSwitcherView) reInflateStub(
-                        R.id.keyguard_user_switcher_view /* viewId */,
-                        R.id.keyguard_user_switcher_stub /* stubId */,
-                        R.layout.keyguard_user_switcher /* layoutId */,
-                        showKeyguardUserSwitcher /* enabled */);
-
-        updateViewControllers(userAvatarView, keyguardUserSwitcherView);
+        updateStatusViewController();
         mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
                 mStatusBarStateController.getInterpolatedDozeAmount());
 
@@ -1424,20 +1332,6 @@
                     false,
                     mBarState);
         }
-        if (mKeyguardQsUserSwitchController != null) {
-            mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
-                    mBarState,
-                    false,
-                    false,
-                    mBarState);
-        }
-        if (mKeyguardUserSwitcherController != null) {
-            mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
-                    mBarState,
-                    false,
-                    false,
-                    mBarState);
-        }
     }
 
     private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
@@ -1563,7 +1457,6 @@
     }
 
     private void updateClockAppearance() {
-        int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard;
         boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
         boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange();
         if (MigrateClocksToBlueprint.isEnabled()) {
@@ -1573,11 +1466,6 @@
                     shouldAnimateClockChange);
         }
         updateKeyguardStatusViewAlignment(/* animate= */true);
-        int userSwitcherHeight = mKeyguardQsUserSwitchController != null
-                ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0;
-        if (mKeyguardUserSwitcherController != null) {
-            userSwitcherHeight = mKeyguardUserSwitcherController.getHeight();
-        }
         float expandedFraction =
                 mScreenOffAnimationController.shouldExpandNotifications()
                         ? 1.0f : getExpandedFraction();
@@ -1595,8 +1483,6 @@
                 mStatusBarHeaderHeightKeyguard,
                 expandedFraction,
                 mKeyguardStatusViewController.getLockscreenHeight(),
-                userSwitcherHeight,
-                userSwitcherPreferredY,
                 darkAmount, mOverStretchAmount,
                 bypassEnabled,
                 mQsController.getHeaderHeight(),
@@ -1621,18 +1507,6 @@
                     mClockPositionResult.clockX, mClockPositionResult.clockY,
                     mClockPositionResult.clockScale, animateClock);
         }
-        if (mKeyguardQsUserSwitchController != null) {
-            mKeyguardQsUserSwitchController.updatePosition(
-                    mClockPositionResult.clockX,
-                    mClockPositionResult.userSwitchY,
-                    animateClock);
-        }
-        if (mKeyguardUserSwitcherController != null) {
-            mKeyguardUserSwitcherController.updatePosition(
-                    mClockPositionResult.clockX,
-                    mClockPositionResult.userSwitchY,
-                    animateClock);
-        }
         updateNotificationTranslucency();
         updateClock();
     }
@@ -1868,13 +1742,6 @@
             mKeyguardStatusViewController
                 .setTranslationY(mKeyguardOnlyTransitionTranslationY, /* excludeMedia= */true);
         }
-
-        if (mKeyguardQsUserSwitchController != null) {
-            mKeyguardQsUserSwitchController.setAlpha(alpha);
-        }
-        if (mKeyguardUserSwitcherController != null) {
-            mKeyguardUserSwitcherController.setAlpha(alpha);
-        }
     }
 
     @Override
@@ -3248,24 +3115,6 @@
     }
 
     @Override
-    public void startBouncerPreHideAnimation() {
-        if (mKeyguardQsUserSwitchController != null) {
-            mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
-                    mBarState,
-                    true /* keyguardFadingAway */,
-                    false /* goingToFullShade */,
-                    mBarState);
-        }
-        if (mKeyguardUserSwitcherController != null) {
-            mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
-                    mBarState,
-                    true /* keyguardFadingAway */,
-                    false /* goingToFullShade */,
-                    mBarState);
-        }
-    }
-
-    @Override
     public ShadeFoldAnimatorImpl getShadeFoldAnimator() {
         return mShadeFoldAnimator;
     }
@@ -3394,8 +3243,6 @@
         ipw.println(mMaxAllowedKeyguardNotifications);
         ipw.print("mAnimateNextPositionUpdate="); ipw.println(mAnimateNextPositionUpdate);
         ipw.print("isPanelExpanded()="); ipw.println(isPanelExpanded());
-        ipw.print("mKeyguardQsUserSwitchEnabled="); ipw.println(mKeyguardQsUserSwitchEnabled);
-        ipw.print("mKeyguardUserSwitcherEnabled="); ipw.println(mKeyguardUserSwitcherEnabled);
         ipw.print("mDozing="); ipw.println(mDozing);
         ipw.print("mDozingOnDown="); ipw.println(mDozingOnDown);
         ipw.print("mBouncerShowing="); ipw.println(mBouncerShowing);
@@ -3552,31 +3399,6 @@
     }
 
     @Override
-    public boolean closeUserSwitcherIfOpen() {
-        if (mKeyguardUserSwitcherController != null) {
-            return mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(
-                    true /* animate */);
-        }
-        return false;
-    }
-
-    private void updateUserSwitcherFlags() {
-        mKeyguardUserSwitcherEnabled = mResources.getBoolean(
-                com.android.internal.R.bool.config_keyguardUserSwitcher);
-        mKeyguardQsUserSwitchEnabled =
-                mKeyguardUserSwitcherEnabled
-                        && mFeatureFlags.isEnabled(Flags.QS_USER_DETAIL_SHORTCUT);
-    }
-
-    private void registerSettingsChangeListener() {
-        mContentResolver.registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.USER_SWITCHER_ENABLED),
-                /* notifyForDescendants */ false,
-                mSettingsChangeObserver
-        );
-    }
-
-    @Override
     public void updateSystemUiStateFlags() {
         if (SysUiState.DEBUG) {
             Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
@@ -4261,13 +4083,6 @@
         }
     }
 
-    private void onQsStateUpdated(boolean qsExpanded, boolean isStackScrollerOverscrolling) {
-        if (mKeyguardUserSwitcherController != null && qsExpanded
-                && !isStackScrollerOverscrolling) {
-            mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(true);
-        }
-    }
-
     private void onQsClippingImmediatelyApplied(boolean clipStatusView,
             Rect lastQsClipBounds, int top, boolean qsFragmentCreated, boolean qsVisible) {
         if (qsFragmentCreated) {
@@ -4383,44 +4198,12 @@
         }
 
         @Override
-        public void onSmallestScreenWidthChanged() {
-            Trace.beginSection("onSmallestScreenWidthChanged");
-            debugLog("onSmallestScreenWidthChanged");
-
-            // Can affect multi-user switcher visibility as it depends on screen size by default:
-            // it is enabled only for devices with large screens (see config_keyguardUserSwitcher)
-            boolean prevKeyguardUserSwitcherEnabled = mKeyguardUserSwitcherEnabled;
-            boolean prevKeyguardQsUserSwitchEnabled = mKeyguardQsUserSwitchEnabled;
-            updateUserSwitcherFlags();
-            if (prevKeyguardUserSwitcherEnabled != mKeyguardUserSwitcherEnabled
-                    || prevKeyguardQsUserSwitchEnabled != mKeyguardQsUserSwitchEnabled) {
-                reInflateViews();
-            }
-
-            Trace.endSection();
-        }
-
-        @Override
         public void onDensityOrFontScaleChanged() {
             debugLog("onDensityOrFontScaleChanged");
             reInflateViews();
         }
     }
 
-    private final class SettingsChangeObserver extends ContentObserver {
-        SettingsChangeObserver(Handler handler) {
-            super(handler);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            debugLog("onSettingsChanged");
-
-            // Can affect multi-user switcher visibility
-            reInflateViews();
-        }
-    }
-
     private final class StatusBarStateListener implements StateListener {
         @Override
         public void onStateChanged(int statusBarState) {
@@ -4592,12 +4375,10 @@
             mConfigurationListener.onThemeChanged();
             mFalsingManager.addTapListener(mFalsingTapListener);
             mKeyguardIndicationController.init();
-            registerSettingsChangeListener();
         }
 
         @Override
         public void onViewDetachedFromWindow(View v) {
-            mContentResolver.unregisterContentObserver(mSettingsChangeObserver);
             mFragmentService.getFragmentHostManager(mView)
                     .removeTagListener(QS.TAG, mQsController.getQsFragmentListener());
             mStatusBarStateController.removeCallback(mStatusBarStateListener);
@@ -4724,13 +4505,6 @@
             }
 
             mKeyguardInteractor.setAlpha(alpha);
-
-            if (mKeyguardQsUserSwitchController != null) {
-                mKeyguardQsUserSwitchController.setAlpha(alpha);
-            }
-            if (mKeyguardUserSwitcherController != null) {
-                mKeyguardUserSwitcherController.setAlpha(alpha);
-            }
         };
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 53617d0..f65d378 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -55,19 +55,12 @@
 
     override fun startExpandLatencyTracking() {}
 
-    override fun startBouncerPreHideAnimation() {}
-
     override fun dozeTimeTick() {}
 
     override fun resetViews(animate: Boolean) {}
 
     override val barState: Int = 0
 
-    @Deprecated("Only supported by very old devices that will not adopt scenes.")
-    override fun closeUserSwitcherIfOpen(): Boolean {
-        return false
-    }
-
     override fun onBackPressed() {}
 
     @Deprecated("According to b/318376223, shade predictive back is not be supported.")
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
index 15ea219..faf238c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
@@ -29,17 +29,6 @@
     /** Returns whether the shade can be collapsed. */
     fun canBeCollapsed(): Boolean
 
-    /**
-     * Close the keyguard user switcher if it is open and capable of closing.
-     *
-     * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
-     * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
-     *
-     * @return true if the keyguard user switcher was open, and is now closed
-     */
-    @Deprecated("Only supported by very old devices that will not adopt scenes.")
-    fun closeUserSwitcherIfOpen(): Boolean
-
     /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
     fun onBackPressed()
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
index f151307..884cfc10 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
@@ -49,11 +49,6 @@
         return shadeInteractor.isAnyExpanded.value && !shadeInteractor.isUserInteracting.value
     }
 
-    @Deprecated("Only supported by very old devices that will not adopt scenes.")
-    override fun closeUserSwitcherIfOpen(): Boolean {
-        return false
-    }
-
     override fun onBackPressed() {
         animateCollapseQs(false)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index a3f2c64..f1765e7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -79,11 +79,7 @@
      * The fraction between [0..1] (i.e., percentage) of screen width to consider the threshold
      * between "top-left" and "top-right" for the purposes of dual-shade invocation.
      *
-     * When the dual-shade is not wide, this always returns 0.5 (the top edge is evenly split). On
-     * wide layouts however, a larger fraction is returned because only the area of the system
-     * status icons is considered top-right.
-     *
-     * Note that this fraction only determines the split between the absolute left and right
+     * Note that this fraction only determines the *split* between the absolute left and right
      * directions. In RTL layouts, the "top-start" edge will resolve to "top-right", and "top-end"
      * will resolve to "top-left".
      */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt
index 987c016..f538d74 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractor.kt
@@ -38,9 +38,6 @@
      */
     @Deprecated("Use ShadeInteractor instead") val isExpanded: Boolean
 
-    /** Called before animating Keyguard dismissal, i.e. the animation dismissing the bouncer. */
-    fun startBouncerPreHideAnimation()
-
     /** Called once every minute while dozing. */
     fun dozeTimeTick()
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 2d7476c..d712ece 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade.domain.interactor
 
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -27,7 +28,6 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 class ShadeLockscreenInteractorImpl
@@ -54,10 +54,6 @@
     override val isExpanded
         get() = shadeInteractor.isAnyExpanded.value
 
-    override fun startBouncerPreHideAnimation() {
-        // TODO("b/324280998") Implement replacement or delete
-    }
-
     override fun dozeTimeTick() {
         // TODO("b/383591086") Implement replacement or delete
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
index c838c37..edf503d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
@@ -76,10 +76,8 @@
 
 class ShadeModeInteractorImpl
 @Inject
-constructor(
-    @Application applicationScope: CoroutineScope,
-    private val repository: ShadeRepository,
-) : ShadeModeInteractor {
+constructor(@Application applicationScope: CoroutineScope, repository: ShadeRepository) :
+    ShadeModeInteractor {
 
     override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide
 
@@ -92,15 +90,7 @@
                 initialValue = determineShadeMode(isShadeLayoutWide.value),
             )
 
-    @FloatRange(from = 0.0, to = 1.0)
-    override fun getTopEdgeSplitFraction(): Float {
-        // Note: this implicitly relies on isShadeLayoutWide being hot (i.e. collected). This
-        // assumption allows us to query its value on demand (during swipe source detection) instead
-        // of running another infinite coroutine.
-        // TODO(b/338577208): Instead of being fixed at 0.8f, this should dynamically updated based
-        //  on the position of system-status icons in the status bar.
-        return if (repository.isShadeLayoutWide.value) 0.8f else 0.5f
-    }
+    @FloatRange(from = 0.0, to = 1.0) override fun getTopEdgeSplitFraction(): Float = 0.5f
 
     private fun determineShadeMode(isShadeLayoutWide: Boolean): ShadeMode {
         return when {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipDateTimeView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipDateTimeView.kt
new file mode 100644
index 0000000..6ebeb84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipDateTimeView.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ui.view
+
+import android.content.Context
+import android.content.res.Configuration
+import android.util.AttributeSet
+import android.widget.DateTimeView
+
+/** A [DateTimeView] for chips in the status bar. See also: [ChipTextView]. */
+class ChipDateTimeView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+    DateTimeView(context, attrs) {
+    private val textTruncationHelper = ChipTextTruncationHelper(this)
+
+    override fun onConfigurationChanged(newConfig: Configuration?) {
+        super.onConfigurationChanged(newConfig)
+        textTruncationHelper.onConfigurationChanged()
+    }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        // Evaluate how wide the text *wants* to be if it had unlimited space. This is needed so
+        // that [textTruncationHelper.shouldShowText] works correctly.
+        super.onMeasure(textTruncationHelper.unlimitedWidthMeasureSpec.specInt, heightMeasureSpec)
+
+        if (
+            textTruncationHelper.shouldShowText(
+                desiredTextWidthPx = measuredWidth,
+                widthMeasureSpec = SysuiMeasureSpec(widthMeasureSpec),
+            )
+        ) {
+            // Show the text with the width spec specified by the helper
+            super.onMeasure(textTruncationHelper.widthMeasureSpec.specInt, heightMeasureSpec)
+        } else {
+            // Changing visibility ensures that the content description is not read aloud when the
+            // text isn't displayed.
+            visibility = GONE
+            setMeasuredDimension(0, 0)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt
new file mode 100644
index 0000000..52495eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextTruncationHelper.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ui.view
+
+import android.view.View
+import android.view.View.MeasureSpec
+import android.widget.TextView.resolveSize
+import com.android.systemui.res.R
+
+/**
+ * Helper class to determine when a status bar chip's text should be hidden because it's too long.
+ */
+class ChipTextTruncationHelper(private val view: View) {
+    /** A measure spec for the status bar chip text with an unlimited width. */
+    val unlimitedWidthMeasureSpec =
+        SysuiMeasureSpec(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
+
+    /** The [MeasureSpec] that the view should actually use win [onMeasure]. */
+    lateinit var widthMeasureSpec: SysuiMeasureSpec
+
+    private var maxWidth: Int = 0
+        set(value) {
+            field = value
+            maximumWidthMeasureSpec =
+                SysuiMeasureSpec(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST))
+        }
+
+    /** A measure spec for the status bar chip text with the correct maximum width. */
+    private lateinit var maximumWidthMeasureSpec: SysuiMeasureSpec
+
+    init {
+        maxWidth = fetchMaxWidth()
+    }
+
+    fun onConfigurationChanged() {
+        maxWidth = fetchMaxWidth()
+    }
+
+    /**
+     * Returns true if this view should show the text because there's enough room for a substantial
+     * amount of text, and returns false if this view should hide the text because the text is much
+     * too long.
+     *
+     * @param desiredTextWidthPx should be calculated by having the view measure itself with
+     *   [unlimitedWidthMeasureSpec] and then sending its `measuredWidth` to this method. (This
+     *   class can't compute [desiredTextWidthPx] directly because [View.onMeasure] can only be
+     *   called by the view itself.)
+     * @param widthMeasureSpec the view's current and unmodified width spec
+     */
+    fun shouldShowText(desiredTextWidthPx: Int, widthMeasureSpec: SysuiMeasureSpec): Boolean {
+        // Evaluate how wide the text *can* be based on:
+        // #1: The maximum width encoded by [maxWidth]
+        val maxWidthBasedOnDimension =
+            resolveSize(desiredTextWidthPx, maximumWidthMeasureSpec.specInt)
+        // #2: The width the view is allowed to take up (If there's 2 chips, the second chip likely
+        // has < [maxWidth] room available)
+        val maxWidthBasedOnViewSpaceAvailable =
+            resolveSize(desiredTextWidthPx, widthMeasureSpec.specInt)
+
+        val enforcedTextWidth: Int
+        if (maxWidthBasedOnViewSpaceAvailable < maxWidthBasedOnDimension) {
+            // View space available takes priority
+            this.widthMeasureSpec = widthMeasureSpec
+            enforcedTextWidth = maxWidthBasedOnViewSpaceAvailable
+        } else {
+            // Enforce the maximum width
+            this.widthMeasureSpec = maximumWidthMeasureSpec
+            enforcedTextWidth = maxWidthBasedOnDimension
+        }
+
+        // Only show the text if at least 50% of it can show. (Assume that if < 50% of the text will
+        // be visible, the text will be more confusing than helpful.)
+        return desiredTextWidthPx <= enforcedTextWidth * 2
+    }
+
+    private fun fetchMaxWidth() =
+        view.context.resources.getDimensionPixelSize(R.dimen.ongoing_activity_chip_max_text_width)
+}
+
+/** A typed class for [MeasureSpec] ints. */
+data class SysuiMeasureSpec(val specInt: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextView.kt
new file mode 100644
index 0000000..3bcc9c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipTextView.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ui.view
+
+import android.content.Context
+import android.content.res.Configuration
+import android.util.AttributeSet
+import android.widget.TextView
+
+/** A [TextView] for chips in the status bar. See also: [ChipDateTimeView]. */
+class ChipTextView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
+    TextView(context, attrs, defStyle) {
+    private val textTruncationHelper = ChipTextTruncationHelper(this)
+
+    override fun onConfigurationChanged(newConfig: Configuration?) {
+        super.onConfigurationChanged(newConfig)
+        textTruncationHelper.onConfigurationChanged()
+    }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        // Evaluate how wide the text *wants* to be if it had unlimited space. This is needed so
+        // that [textTruncationHelper.shouldShowText] works correctly.
+        super.onMeasure(textTruncationHelper.unlimitedWidthMeasureSpec.specInt, heightMeasureSpec)
+
+        if (
+            textTruncationHelper.shouldShowText(
+                desiredTextWidthPx = measuredWidth,
+                widthMeasureSpec = SysuiMeasureSpec(widthMeasureSpec),
+            )
+        ) {
+            // Show the text with the width spec specified by the helper
+            super.onMeasure(textTruncationHelper.widthMeasureSpec.specInt, heightMeasureSpec)
+        } else {
+            // Changing visibility ensures that the content description is not read aloud when the
+            // text isn't displayed.
+            visibility = GONE
+            setMeasuredDimension(0, 0)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt
index de369c3..4289dab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt
@@ -74,7 +74,8 @@
      */
     fun parse(args: List<String>): Boolean {
         if (args.isEmpty()) {
-            return false
+            // An empty args list might be valid here if there are no required inputs
+            return validateRequiredParams()
         }
 
         val iterator = args.listIterator()
@@ -268,11 +269,7 @@
         _subCommands.add(new)
     }
 
-    internal fun flag(
-        longName: String,
-        shortName: String? = null,
-        description: String = "",
-    ): Flag {
+    internal fun flag(longName: String, shortName: String? = null, description: String = ""): Flag {
         checkCliNames(shortName, longName)?.let {
             throw IllegalArgumentException("Detected reused flag name ($it)")
         }
@@ -305,9 +302,7 @@
         return param
     }
 
-    internal fun <T : ParseableCommand> subCommand(
-        command: T,
-    ): OptionalSubCommand<T> {
+    internal fun <T : ParseableCommand> subCommand(command: T): OptionalSubCommand<T> {
         checkCliNames(null, command.name)?.let {
             throw IllegalArgumentException("Cannot re-use name for subcommand ($it)")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 7df7ef1..254b792 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -63,6 +63,7 @@
 import com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl;
 import com.android.systemui.statusbar.phone.ui.StatusBarIconList;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.wm.shell.shared.ShellTransitions;
 
 import dagger.Binds;
 import dagger.Lazy;
@@ -214,8 +215,8 @@
     @Provides
     @SysUISingleton
     static ActivityTransitionAnimator provideActivityTransitionAnimator(
-            @Main Executor mainExecutor) {
-        return new ActivityTransitionAnimator(mainExecutor);
+            @Main Executor mainExecutor, ShellTransitions shellTransitions) {
+        return new ActivityTransitionAnimator(mainExecutor, shellTransitions);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 878a4aa..70e27a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -203,18 +203,20 @@
         result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(),
                 packageContext, row.getExistingSmartReplyState(), smartRepliesInflater, mLogger);
         boolean isConversation = entry.getRanking().isConversation();
+        Notification.MessagingStyle messagingStyle = null;
+        if (isConversation && (AsyncHybridViewInflation.isEnabled()
+                || LockscreenOtpRedaction.isSingleLineViewEnabled())) {
+            messagingStyle = mConversationProcessor
+                    .processNotification(entry, builder, mLogger);
+        }
         if (AsyncHybridViewInflation.isEnabled()) {
-            Notification.MessagingStyle messagingStyle = null;
-            if (isConversation) {
-                messagingStyle = mConversationProcessor
-                        .processNotification(entry, builder, mLogger);
-            }
             SingleLineViewModel viewModel = SingleLineViewInflater
                     .inflateSingleLineViewModel(
                             entry.getSbn().getNotification(),
                             messagingStyle,
                             builder,
-                            row.getContext()
+                            row.getContext(),
+                            false
                     );
             // If the messagingStyle is null, we want to inflate the normal view
             isConversation = viewModel.isConversation();
@@ -228,11 +230,22 @@
                             mLogger
                     );
         }
-
         if (LockscreenOtpRedaction.isSingleLineViewEnabled()) {
-            result.mPublicInflatedSingleLineViewModel =
-                    SingleLineViewInflater.inflateRedactedSingleLineViewModel(row.getContext(),
-                            isConversation);
+            if (bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
+                result.mPublicInflatedSingleLineViewModel =
+                        SingleLineViewInflater.inflateSingleLineViewModel(
+                                entry.getSbn().getNotification(),
+                                messagingStyle,
+                                builder,
+                                row.getContext(),
+                                true);
+            } else {
+                result.mPublicInflatedSingleLineViewModel =
+                        SingleLineViewInflater.inflateRedactedSingleLineViewModel(
+                                row.getContext(),
+                                isConversation
+                        );
+            }
             result.mPublicInflatedSingleLineView =
                     SingleLineViewInflater.inflatePublicSingleLineView(
                             isConversation,
@@ -1300,7 +1313,8 @@
                                 mEntry.getSbn().getNotification(),
                                 messagingStyle,
                                 recoveredBuilder,
-                                mContext
+                                mContext,
+                                false
                         );
                 result.mInflatedSingleLineView =
                         SingleLineViewInflater.inflatePrivateSingleLineView(
@@ -1313,9 +1327,22 @@
             }
 
             if (LockscreenOtpRedaction.isSingleLineViewEnabled()) {
-                result.mPublicInflatedSingleLineViewModel =
-                        SingleLineViewInflater.inflateRedactedSingleLineViewModel(mContext,
-                                isConversation);
+                if (mBindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
+                    result.mPublicInflatedSingleLineViewModel =
+                            SingleLineViewInflater.inflateSingleLineViewModel(
+                                    mEntry.getSbn().getNotification(),
+                                    messagingStyle,
+                                    recoveredBuilder,
+                                    mContext,
+                                    true
+                            );
+                } else {
+                    result.mPublicInflatedSingleLineViewModel =
+                            SingleLineViewInflater.inflateRedactedSingleLineViewModel(
+                                    mContext,
+                                    isConversation
+                            );
+                }
                 result.mPublicInflatedSingleLineView =
                         SingleLineViewInflater.inflatePublicSingleLineView(
                                 isConversation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index e4e1398..c619b17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -718,6 +718,7 @@
                         messagingStyle = messagingStyle,
                         builder = builder,
                         systemUiContext = systemUiContext,
+                        redactText = false,
                     )
                 } else null
 
@@ -727,10 +728,20 @@
                         reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE != 0
                 ) {
                     logger.logAsyncTaskProgress(entry, "inflating public single line view model")
-                    SingleLineViewInflater.inflateRedactedSingleLineViewModel(
-                        systemUiContext,
-                        entry.ranking.isConversation,
-                    )
+                    if (bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
+                        SingleLineViewInflater.inflateSingleLineViewModel(
+                            notification = entry.sbn.notification,
+                            messagingStyle = messagingStyle,
+                            builder = builder,
+                            systemUiContext = systemUiContext,
+                            redactText = true,
+                        )
+                    } else {
+                        SingleLineViewInflater.inflateRedactedSingleLineViewModel(
+                            systemUiContext,
+                            entry.ranking.isConversation,
+                        )
+                    }
                 } else null
 
             val headsUpStatusBarModel =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
index e702f10..fe2803b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
@@ -51,6 +51,7 @@
      *   notification, not for legacy messaging notifications
      * @param builder the recovered Notification Builder
      * @param systemUiContext the context of Android System UI
+     * @param redactText indicates if the text needs to be redacted
      * @return the inflated SingleLineViewModel
      */
     @JvmStatic
@@ -59,13 +60,21 @@
         messagingStyle: MessagingStyle?,
         builder: Notification.Builder,
         systemUiContext: Context,
+        redactText: Boolean,
     ): SingleLineViewModel {
         if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
             return SingleLineViewModel(null, null, null)
         }
         peopleHelper.init(systemUiContext)
         var titleText = HybridGroupManager.resolveTitle(notification)
-        var contentText = HybridGroupManager.resolveText(notification)
+        var contentText =
+            if (redactText) {
+                systemUiContext.getString(
+                    com.android.systemui.res.R.string.redacted_notification_single_line_text
+                )
+            } else {
+                HybridGroupManager.resolveText(notification)
+            }
 
         if (messagingStyle == null) {
             return SingleLineViewModel(
@@ -81,7 +90,7 @@
         if (conversationTextData?.conversationTitle?.isNotEmpty() == true) {
             titleText = conversationTextData.conversationTitle
         }
-        if (conversationTextData?.conversationText?.isNotEmpty() == true) {
+        if (!redactText && conversationTextData?.conversationText?.isNotEmpty() == true) {
             contentText = conversationTextData.conversationText
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 86c7c6b..4751293 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -20,6 +20,7 @@
 import android.os.UserHandle
 import android.view.View
 import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.TransitionAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
@@ -38,7 +39,7 @@
     private val statusBarStateController: SysuiStatusBarStateController,
     @Main private val mainExecutor: DelayableExecutor,
     activityStarterInternal: Lazy<ActivityStarterInternalImpl>,
-    legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>
+    legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>,
 ) : ActivityStarter {
 
     private val activityStarterInternal: ActivityStarterInternal =
@@ -48,10 +49,23 @@
             legacyActivityStarter.get()
         }
 
+    override fun registerTransition(
+        cookie: ActivityTransitionAnimator.TransitionCookie,
+        controllerFactory: ActivityTransitionAnimator.ControllerFactory,
+    ) {
+        if (!TransitionAnimator.longLivedReturnAnimationsEnabled()) return
+        activityStarterInternal.registerTransition(cookie, controllerFactory)
+    }
+
+    override fun unregisterTransition(cookie: ActivityTransitionAnimator.TransitionCookie) {
+        if (!TransitionAnimator.longLivedReturnAnimationsEnabled()) return
+        activityStarterInternal.unregisterTransition(cookie)
+    }
+
     override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) {
         activityStarterInternal.startPendingIntentDismissingKeyguard(
             intent = intent,
-            dismissShade = true
+            dismissShade = true,
         )
     }
 
@@ -98,7 +112,7 @@
         intentSentUiThreadCallback: Runnable?,
         animationController: ActivityTransitionAnimator.Controller?,
         fillInIntent: Intent?,
-        extraOptions: Bundle?
+        extraOptions: Bundle?,
     ) {
         activityStarterInternal.startPendingIntentDismissingKeyguard(
             intent = intent,
@@ -115,7 +129,7 @@
     override fun startPendingIntentMaybeDismissingKeyguard(
         intent: PendingIntent,
         intentSentUiThreadCallback: Runnable?,
-        animationController: ActivityTransitionAnimator.Controller?
+        animationController: ActivityTransitionAnimator.Controller?,
     ) {
         activityStarterInternal.startPendingIntentDismissingKeyguard(
             intent = intent,
@@ -245,7 +259,7 @@
 
     override fun postStartActivityDismissingKeyguard(
         intent: PendingIntent,
-        animationController: ActivityTransitionAnimator.Controller?
+        animationController: ActivityTransitionAnimator.Controller?,
     ) {
         postOnUiThread {
             activityStarterInternal.startPendingIntentDismissingKeyguard(
@@ -381,7 +395,7 @@
         postOnUiThread {
             statusBarStateController.setLeaveOpenOnKeyguardHide(true)
             activityStarterInternal.executeRunnableDismissingKeyguard(
-                runnable = { runnable?.let { postOnUiThread(runnable = it) } },
+                runnable = { runnable?.let { postOnUiThread(runnable = it) } }
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
index 93ce6e8..5e427fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone
 
 import android.app.PendingIntent
+import android.content.ComponentName
 import android.content.Intent
 import android.os.Bundle
 import android.os.UserHandle
@@ -27,6 +28,21 @@
 
 interface ActivityStarterInternal {
     /**
+     * Registers the given [controllerFactory] for launching and closing transitions matching the
+     * [cookie] and the [ComponentName] that it contains.
+     */
+    fun registerTransition(
+        cookie: ActivityTransitionAnimator.TransitionCookie,
+        controllerFactory: ActivityTransitionAnimator.ControllerFactory,
+    )
+
+    /**
+     * Unregisters the [ActivityTransitionAnimator.Controller] previously registered containing the
+     * given [cookie]. If no such registration exists, this is a no-op.
+     */
+    fun unregisterTransition(cookie: ActivityTransitionAnimator.TransitionCookie)
+
+    /**
      * Starts a pending intent after dismissing keyguard.
      *
      * This can be called in a background thread (to prevent calls in [ActivityIntentHelper] in the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index f2ef2f0..33e4fed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.Flags
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.animation.DelegateTransitionAnimatorController
+import com.android.systemui.animation.TransitionAnimator
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.camera.CameraIntents
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -103,6 +104,44 @@
     private val centralSurfaces: CentralSurfaces?
         get() = centralSurfacesOptLazy.get().getOrNull()
 
+    override fun registerTransition(
+        cookie: ActivityTransitionAnimator.TransitionCookie,
+        controllerFactory: ActivityTransitionAnimator.ControllerFactory,
+    ) {
+        check(TransitionAnimator.longLivedReturnAnimationsEnabled())
+
+        val factory =
+            object :
+                ActivityTransitionAnimator.ControllerFactory(
+                    controllerFactory.cookie,
+                    controllerFactory.component,
+                    controllerFactory.launchCujType,
+                    controllerFactory.returnCujType,
+                ) {
+                override fun createController(
+                    forLaunch: Boolean
+                ): ActivityTransitionAnimator.Controller {
+                    val baseController = controllerFactory.createController(forLaunch)
+                    val rootView = baseController.transitionContainer.rootView
+                    val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> =
+                        statusBarWindowControllerStore.defaultDisplay
+                            .wrapAnimationControllerIfInStatusBar(rootView, baseController)
+                    return if (controllerFromStatusBar.isPresent) {
+                        controllerFromStatusBar.get()
+                    } else {
+                        baseController
+                    }
+                }
+            }
+
+        activityTransitionAnimator.register(cookie, factory)
+    }
+
+    override fun unregisterTransition(cookie: ActivityTransitionAnimator.TransitionCookie) {
+        check(TransitionAnimator.longLivedReturnAnimationsEnabled())
+        activityTransitionAnimator.unregister(cookie)
+    }
+
     override fun startPendingIntentDismissingKeyguard(
         intent: PendingIntent,
         dismissShade: Boolean,
@@ -134,7 +173,7 @@
                 (skipLockscreenChecks ||
                     activityIntentHelper.wouldPendingShowOverLockscreen(
                         intent,
-                        lockScreenUserManager.currentUserId
+                        lockScreenUserManager.currentUserId,
                     ))
 
         val animate =
@@ -190,7 +229,7 @@
                                 null,
                                 null,
                                 null,
-                                options.toBundle()
+                                options.toBundle(),
                             )
                         }
                     },
@@ -239,7 +278,7 @@
         animationController: ActivityTransitionAnimator.Controller?,
         customMessage: String?,
         disallowEnterPictureInPictureWhileLaunching: Boolean,
-        userHandle: UserHandle?
+        userHandle: UserHandle?,
     ) {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
         val userHandle: UserHandle = userHandle ?: getActivityUserHandle(intent)
@@ -280,7 +319,7 @@
             activityTransitionAnimator.startIntentWithAnimation(
                 animController,
                 animate,
-                intent.getPackage()
+                intent.getPackage(),
             ) { adapter: RemoteAnimationAdapter? ->
                 val options =
                     ActivityOptions(CentralSurfaces.getActivityOptions(displayId, adapter))
@@ -359,7 +398,7 @@
         dismissShade: Boolean,
         animationController: ActivityTransitionAnimator.Controller?,
         showOverLockscreenWhenLocked: Boolean,
-        userHandle: UserHandle?
+        userHandle: UserHandle?,
     ) {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
         val userHandle = userHandle ?: getActivityUserHandle(intent)
@@ -383,7 +422,7 @@
             animationController != null &&
                 shouldAnimateLaunch(
                     isActivityIntent = true,
-                    showOverLockscreen = showOverLockscreenWhenLocked
+                    showOverLockscreen = showOverLockscreenWhenLocked,
                 )
 
         var controller: ActivityTransitionAnimator.Controller? = null
@@ -413,7 +452,7 @@
             controller,
             animate,
             intent.getPackage(),
-            showOverLockscreenWhenLocked
+            showOverLockscreenWhenLocked,
         ) { adapter: RemoteAnimationAdapter? ->
             TaskStackBuilder.create(context)
                 .addNextIntent(intent)
@@ -425,7 +464,7 @@
         action: ActivityStarter.OnDismissAction,
         cancel: Runnable?,
         afterKeyguardGone: Boolean,
-        customMessage: String?
+        customMessage: String?,
     ) {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
         Log.i(TAG, "Invoking dismissKeyguardThenExecute, afterKeyguardGone: $afterKeyguardGone")
@@ -453,7 +492,7 @@
         afterKeyguardGone: Boolean,
         deferred: Boolean,
         willAnimateOnKeyguard: Boolean,
-        customMessage: String?
+        customMessage: String?,
     ) {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
         val onDismissAction: ActivityStarter.OnDismissAction =
@@ -482,12 +521,7 @@
                     return willAnimateOnKeyguard
                 }
             }
-        dismissKeyguardThenExecute(
-            onDismissAction,
-            cancelAction,
-            afterKeyguardGone,
-            customMessage,
-        )
+        dismissKeyguardThenExecute(onDismissAction, cancelAction, afterKeyguardGone, customMessage)
     }
 
     override fun shouldAnimateLaunch(isActivityIntent: Boolean): Boolean {
@@ -565,7 +599,7 @@
         val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> =
             statusBarWindowControllerStore.defaultDisplay.wrapAnimationControllerIfInStatusBar(
                 rootView,
-                animationController
+                animationController,
             )
         if (controllerFromStatusBar.isPresent) {
             return controllerFromStatusBar.get()
@@ -582,7 +616,7 @@
                     notifShadeWindowControllerLazy.get(),
                     commandQueue,
                     displayId,
-                    isLaunchForActivity
+                    isLaunchForActivity,
                 )
             }
         }
@@ -596,7 +630,7 @@
      */
     private fun wrapAnimationControllerForLockscreen(
         dismissShade: Boolean,
-        animationController: ActivityTransitionAnimator.Controller?
+        animationController: ActivityTransitionAnimator.Controller?,
     ): ActivityTransitionAnimator.Controller? {
         return animationController?.let {
             object : DelegateTransitionAnimatorController(it) {
@@ -613,7 +647,7 @@
                         communalSceneInteractor.snapToScene(
                             newScene = CommunalScenes.Blank,
                             loggingReason = "ActivityStarterInternalImpl",
-                            delayMillis = ActivityTransitionAnimator.TIMINGS.totalDuration
+                            delayMillis = ActivityTransitionAnimator.TIMINGS.totalDuration,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 013903a..518923e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -33,7 +33,6 @@
 import com.android.systemui.res.R;
 import com.android.systemui.shade.LargeScreenHeaderHelper;
 import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
 
 import javax.inject.Inject;
 
@@ -55,19 +54,6 @@
     private int mKeyguardStatusHeight;
 
     /**
-     * Height of user avatar used by the multi-user switcher. This could either be the
-     * {@link KeyguardUserSwitcherListView} when it is closed and only the current user's icon is
-     * visible, or it could be height of the avatar used by the
-     * {@link com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController}.
-     */
-    private int mUserSwitchHeight;
-
-    /**
-     * Preferred Y position of user avatar used by the multi-user switcher.
-     */
-    private int mUserSwitchPreferredY;
-
-    /**
      * Minimum top margin to avoid overlap with status bar or multi-user switcher avatar.
      */
     private int mMinTopMargin;
@@ -184,17 +170,13 @@
      * Sets up algorithm values.
      */
     public void setup(int keyguardStatusBarHeaderHeight, float panelExpansion,
-            int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY,
-            float dark, float overStretchAmount, boolean bypassEnabled,
+            int keyguardStatusHeight, float dark, float overStretchAmount, boolean bypassEnabled,
             int unlockedStackScrollerPadding, float qsExpansion, int cutoutTopInset,
             boolean isSplitShade, float udfpsTop, float clockBottom, boolean isClockTopAligned) {
-        mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding,
-                userSwitchHeight);
+        mMinTopMargin = keyguardStatusBarHeaderHeight + mContainerTopPadding;
         mPanelExpansion = BouncerPanelExpansionCalculator
                 .getKeyguardClockScaledExpansion(panelExpansion);
         mKeyguardStatusHeight = keyguardStatusHeight + mStatusViewBottomMargin;
-        mUserSwitchHeight = userSwitchHeight;
-        mUserSwitchPreferredY = userSwitchPreferredY;
         mDarkAmount = dark;
         mOverStretchAmount = overStretchAmount;
         mBypassEnabled = bypassEnabled;
@@ -210,7 +192,6 @@
     public void run(Result result) {
         final int y = getClockY(mPanelExpansion, mDarkAmount);
         result.clockY = y;
-        result.userSwitchY = getUserSwitcherY(mPanelExpansion);
         result.clockYFullyDozing = getClockY(
                 1.0f /* panelExpansion */, 1.0f /* darkAmount */);
         result.clockAlpha = getClockAlpha(y);
@@ -224,7 +205,7 @@
         if (mBypassEnabled) {
             return mUnlockedStackScrollerPadding;
         } else if (mIsSplitShade) {
-            return getClockY(1.0f, mDarkAmount) + mUserSwitchHeight;
+            return getClockY(1.0f, mDarkAmount);
         } else {
             return getClockY(1.0f, mDarkAmount) + mKeyguardStatusHeight;
         }
@@ -236,7 +217,7 @@
         } else if (mIsSplitShade) {
             // mCurrentBurnInOffsetY is subtracted to make notifications not follow clock adjustment
             // for burn-in. It can make pulsing notification go too high and it will get clipped
-            return clockYPosition - mSplitShadeTopNotificationsMargin + mUserSwitchHeight
+            return clockYPosition - mSplitShadeTopNotificationsMargin
                     - (int) mCurrentBurnInOffsetY;
         } else {
             return clockYPosition + mKeyguardStatusHeight;
@@ -251,7 +232,7 @@
         if (mBypassEnabled) {
             return mUnlockedStackScrollerPadding - nsslTop;
         } else if (mIsSplitShade) {
-            return mSplitShadeTargetTopMargin + mUserSwitchHeight - nsslTop;
+            return mSplitShadeTargetTopMargin - nsslTop;
         } else {
             // Non-bypass portrait shade already uses values from nsslTop
             // so we don't need to subtract it here.
@@ -339,17 +320,6 @@
         return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
     }
 
-    private int getUserSwitcherY(float panelExpansion) {
-        float userSwitchYRegular = mUserSwitchPreferredY;
-        float userSwitchYBouncer = -mKeyguardStatusHeight - mUserSwitchHeight;
-
-        // Move user-switch up while collapsing the shade
-        float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion);
-        float userSwitchY = MathUtils.lerp(userSwitchYBouncer, userSwitchYRegular, shadeExpansion);
-
-        return (int) (userSwitchY + mOverStretchAmount);
-    }
-
     /**
      * We might want to fade out the clock when the user is swiping up.
      * One exception is when the bouncer will become visible, in this cause the clock
@@ -391,11 +361,6 @@
         public int clockY;
 
         /**
-         * The y translation of the multi-user switch.
-         */
-        public int userSwitchY;
-
-        /**
          * The y translation of the clock when we're fully dozing.
          */
         public int clockYFullyDozing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index d7cc65d..d7a29c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.Flags.mediaLockscreenLaunchAnimation
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.animation.DelegateTransitionAnimatorController
+import com.android.systemui.animation.TransitionAnimator
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.camera.CameraIntents
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -98,6 +99,44 @@
     private val centralSurfaces: CentralSurfaces?
         get() = centralSurfacesOptLazy.get().getOrNull()
 
+    override fun registerTransition(
+        cookie: ActivityTransitionAnimator.TransitionCookie,
+        controllerFactory: ActivityTransitionAnimator.ControllerFactory,
+    ) {
+        check(TransitionAnimator.longLivedReturnAnimationsEnabled())
+
+        val factory =
+            object :
+                ActivityTransitionAnimator.ControllerFactory(
+                    controllerFactory.cookie,
+                    controllerFactory.component,
+                    controllerFactory.launchCujType,
+                    controllerFactory.returnCujType,
+                ) {
+                override fun createController(
+                    forLaunch: Boolean
+                ): ActivityTransitionAnimator.Controller {
+                    val baseController = controllerFactory.createController(forLaunch)
+                    val rootView = baseController.transitionContainer.rootView
+                    val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> =
+                        statusBarWindowControllerStore.defaultDisplay
+                            .wrapAnimationControllerIfInStatusBar(rootView, baseController)
+                    return if (controllerFromStatusBar.isPresent) {
+                        controllerFromStatusBar.get()
+                    } else {
+                        baseController
+                    }
+                }
+            }
+
+        activityTransitionAnimator.register(cookie, factory)
+    }
+
+    override fun unregisterTransition(cookie: ActivityTransitionAnimator.TransitionCookie) {
+        check(TransitionAnimator.longLivedReturnAnimationsEnabled())
+        activityTransitionAnimator.unregister(cookie)
+    }
+
     override fun startActivityDismissingKeyguard(
         intent: Intent,
         dismissShade: Boolean,
@@ -116,7 +155,7 @@
         val willLaunchResolverActivity: Boolean =
             activityIntentHelper.wouldLaunchResolverActivity(
                 intent,
-                lockScreenUserManager.currentUserId
+                lockScreenUserManager.currentUserId,
             )
 
         val animate =
@@ -147,7 +186,7 @@
             activityTransitionAnimator.startIntentWithAnimation(
                 animController,
                 animate,
-                intent.getPackage()
+                intent.getPackage(),
             ) { adapter: RemoteAnimationAdapter? ->
                 val options =
                     ActivityOptions(CentralSurfaces.getActivityOptions(displayId, adapter))
@@ -259,7 +298,7 @@
                 (skipLockscreenChecks ||
                     activityIntentHelper.wouldPendingShowOverLockscreen(
                         intent,
-                        lockScreenUserManager.currentUserId
+                        lockScreenUserManager.currentUserId,
                     ))
 
         val animate =
@@ -317,7 +356,7 @@
                                 null,
                                 null,
                                 null,
-                                options.toBundle()
+                                options.toBundle(),
                             )
                         }
                     },
@@ -409,7 +448,7 @@
             controller,
             animate,
             intent.getPackage(),
-            showOverLockscreenWhenLocked
+            showOverLockscreenWhenLocked,
         ) { adapter: RemoteAnimationAdapter? ->
             TaskStackBuilder.create(context)
                 .addNextIntent(intent)
@@ -495,12 +534,7 @@
                     return willAnimateOnKeyguard
                 }
             }
-        dismissKeyguardThenExecute(
-            onDismissAction,
-            cancelAction,
-            afterKeyguardGone,
-            customMessage,
-        )
+        dismissKeyguardThenExecute(onDismissAction, cancelAction, afterKeyguardGone, customMessage)
     }
 
     /**
@@ -528,7 +562,7 @@
         val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> =
             statusBarWindowControllerStore.defaultDisplay.wrapAnimationControllerIfInStatusBar(
                 rootView,
-                animationController
+                animationController,
             )
         if (controllerFromStatusBar.isPresent) {
             return controllerFromStatusBar.get()
@@ -545,7 +579,7 @@
                     notifShadeWindowControllerLazy.get(),
                     commandQueue,
                     displayId,
-                    isLaunchForActivity
+                    isLaunchForActivity,
                 )
             }
         }
@@ -559,7 +593,7 @@
      */
     private fun wrapAnimationControllerForLockscreen(
         dismissShade: Boolean,
-        animationController: ActivityTransitionAnimator.Controller?
+        animationController: ActivityTransitionAnimator.Controller?,
     ): ActivityTransitionAnimator.Controller? {
         return animationController?.let {
             object : DelegateTransitionAnimatorController(it) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index bd1360f..3749b96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1178,7 +1178,6 @@
     public void startPreHideAnimation(Runnable finishRunnable) {
         if (primaryBouncerIsShowing()) {
             mPrimaryBouncerInteractor.startDisappearAnimation(finishRunnable);
-            mShadeLockscreenInteractor.startBouncerPreHideAnimation();
 
             // We update the state (which will show the keyguard) only if an animation will run on
             // the keyguard. If there is no animation, we wait before updating the state so that we
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index f37bc6b..4d1d64e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -37,10 +37,12 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.InitController;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeViewController;
@@ -59,6 +61,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition;
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
@@ -69,7 +72,6 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.Set;
@@ -102,6 +104,7 @@
     private final IStatusBarService mBarService;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final NotificationListContainer mNotifListContainer;
+    private final DeviceUnlockedInteractor mDeviceUnlockedInteractor;
     private final QuickSettingsController mQsController;
 
     protected boolean mVrMode;
@@ -133,7 +136,8 @@
             VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
             NotificationRemoteInputManager remoteInputManager,
             NotificationRemoteInputManager.Callback remoteInputManagerCallback,
-            NotificationListContainer notificationListContainer) {
+            NotificationListContainer notificationListContainer,
+            DeviceUnlockedInteractor deviceUnlockedInteractor) {
         mActivityStarter = activityStarter;
         mKeyguardStateController = keyguardStateController;
         mNotificationPanel = panel;
@@ -160,6 +164,7 @@
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mNotifListContainer = notificationListContainer;
+        mDeviceUnlockedInteractor = deviceUnlockedInteractor;
 
         IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
                 Context.VR_SERVICE));
@@ -246,16 +251,27 @@
         mPowerInteractor.wakeUpIfDozing("NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
         if (nowExpanded) {
             if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
-                mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
-            } else if (clickedEntry.isSensitive().getValue()
-                    && mDynamicPrivacyController.isInLockedDownShade()) {
+                mShadeTransitionController.goToLockedShade(
+                        clickedEntry.getRow(), /* needsQSAnimation = */ true);
+            } else if (clickedEntry.isSensitive().getValue() && isInLockedDownShade()) {
                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
+                // launch the bouncer if the device is locked
                 mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */
                         , null /* cancelRunnable */, false /* afterKeyguardGone */);
             }
         }
     }
 
+    /** @return true if the Shade is shown over the Lockscreen, and the device is locked */
+    private boolean isInLockedDownShade() {
+        if (SceneContainerFlag.isEnabled()) {
+            return mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
+                    && !mDeviceUnlockedInteractor.getDeviceUnlockStatus().getValue().isUnlocked();
+        } else {
+            return mDynamicPrivacyController.isInLockedDownShade();
+        }
+    }
+
     @Override
     public boolean isDeviceInVrMode() {
         return mVrMode;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
deleted file mode 100644
index 3eeb59d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-
-import androidx.core.graphics.ColorUtils;
-
-import com.android.app.animation.Interpolators;
-import com.android.keyguard.KeyguardConstants;
-import com.android.systemui.qs.tiles.UserDetailItemView;
-import com.android.systemui.res.R;
-
-/**
- * Displays a user on the keyguard user switcher.
- */
-public class KeyguardUserDetailItemView extends UserDetailItemView {
-
-    private static final String TAG = "KeyguardUserDetailItemView";
-    private static final boolean DEBUG = KeyguardConstants.DEBUG;
-
-    private static final int ANIMATION_DURATION_FADE_NAME = 240;
-
-    private float mDarkAmount;
-    private int mTextColor;
-
-    public KeyguardUserDetailItemView(Context context) {
-        this(context, null);
-    }
-
-    public KeyguardUserDetailItemView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public KeyguardUserDetailItemView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public KeyguardUserDetailItemView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    @Override
-    protected int getFontSizeDimen() {
-        return R.dimen.kg_user_switcher_text_size;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mTextColor = mName.getCurrentTextColor();
-        updateDark();
-    }
-
-    /**
-     * Update visibility of this view.
-     *
-     * @param showItem If true, this item is visible on the screen to the user. Generally this
-     *                 means that the item would be clickable. If false, item visibility will be
-     *                 set to GONE and hidden entirely.
-     * @param showTextName Whether or not the name should be shown next to the icon. If false,
-     *                     only the icon is shown.
-     * @param animate Whether the transition should be animated. Note, this only applies to
-     *                animating the text name. The item itself will not animate (i.e. fade in/out).
-     *                Instead, we delegate that to the parent view.
-     */
-    void updateVisibilities(boolean showItem, boolean showTextName, boolean animate) {
-        if (DEBUG) {
-            Log.d(TAG, String.format("updateVisibilities itemIsShown=%b nameIsShown=%b animate=%b",
-                    showItem, showTextName, animate));
-        }
-
-        getBackground().setAlpha((showItem && showTextName) ? 255 : 0);
-
-        if (showItem) {
-            if (showTextName) {
-                mName.setVisibility(View.VISIBLE);
-                if (animate) {
-                    mName.setAlpha(0f);
-                    mName.animate()
-                            .alpha(1f)
-                            .setDuration(ANIMATION_DURATION_FADE_NAME)
-                            .setInterpolator(Interpolators.ALPHA_IN);
-                } else {
-                    mName.setAlpha(1f);
-                }
-            } else {
-                if (animate) {
-                    mName.setVisibility(View.VISIBLE);
-                    mName.setAlpha(1f);
-                    mName.animate()
-                            .alpha(0f)
-                            .setDuration(ANIMATION_DURATION_FADE_NAME)
-                            .setInterpolator(Interpolators.ALPHA_OUT)
-                            .withEndAction(() -> {
-                                mName.setVisibility(View.GONE);
-                                mName.setAlpha(1f);
-                            });
-                } else {
-                    mName.setVisibility(View.GONE);
-                    mName.setAlpha(1f);
-                }
-            }
-            setVisibility(View.VISIBLE);
-            setAlpha(1f);
-        } else {
-            // If item isn't shown, don't animate. The parent class will animate the view instead
-            setVisibility(View.GONE);
-            setAlpha(1f);
-            mName.setVisibility(showTextName ? View.VISIBLE : View.GONE);
-            mName.setAlpha(1f);
-        }
-    }
-
-    /**
-     * Set the amount (ratio) that the device has transitioned to doze.
-     *
-     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
-     */
-    public void setDarkAmount(float darkAmount) {
-        if (mDarkAmount == darkAmount) {
-            return;
-        }
-        mDarkAmount = darkAmount;
-        updateDark();
-    }
-
-    private void updateDark() {
-        final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
-        mName.setTextColor(blendedTextColor);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
deleted file mode 100644
index 770f441..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ /dev/null
@@ -1,565 +0,0 @@
-/*
- * Copyright (C) 2021 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.policy;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.DataSetObserver;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.app.animation.Interpolators;
-import com.android.keyguard.KeyguardConstants;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.KeyguardVisibilityHelper;
-import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
-import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.AnimatableProperty;
-import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.user.data.source.UserRecord;
-import com.android.systemui.util.ViewController;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Manages the user switcher on the Keyguard.
- */
-@KeyguardUserSwitcherScope
-@Deprecated
-public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {
-
-    private static final String TAG = "KeyguardUserSwitcherController";
-    private static final boolean DEBUG = KeyguardConstants.DEBUG;
-
-    private static final AnimationProperties ANIMATION_PROPERTIES =
-            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-
-    private final Context mContext;
-    private final UserSwitcherController mUserSwitcherController;
-    private final ScreenLifecycle mScreenLifecycle;
-    private final KeyguardUserAdapter mAdapter;
-    private final KeyguardStateController mKeyguardStateController;
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    protected final SysuiStatusBarStateController mStatusBarStateController;
-    private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
-    private ObjectAnimator mBgAnimator;
-    private final KeyguardUserSwitcherScrim mBackground;
-
-    // Child views of KeyguardUserSwitcherView
-    private KeyguardUserSwitcherListView mListView;
-
-    // State info for the user switcher
-    private boolean mUserSwitcherOpen;
-    private int mCurrentUserId = UserHandle.USER_NULL;
-    private int mBarState;
-    private float mDarkAmount;
-
-    private final KeyguardUpdateMonitorCallback mInfoCallback =
-            new KeyguardUpdateMonitorCallback() {
-                @Override
-                public void onKeyguardVisibilityChanged(boolean visible) {
-                    if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", visible));
-                    // Any time the keyguard is hidden, try to close the user switcher menu to
-                    // restore keyguard to the default state
-                    if (!visible) {
-                        closeSwitcherIfOpenAndNotSimple(false);
-                    }
-                }
-
-                @Override
-                public void onUserSwitching(int userId) {
-                    closeSwitcherIfOpenAndNotSimple(false);
-                }
-            };
-
-    private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
-        @Override
-        public void onScreenTurnedOff() {
-            if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
-            closeSwitcherIfOpenAndNotSimple(false);
-        }
-    };
-
-    private final StatusBarStateController.StateListener mStatusBarStateListener =
-            new StatusBarStateController.StateListener() {
-                @Override
-                public void onStateChanged(int newState) {
-                    if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState));
-
-                    boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
-                    boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
-                    int oldState = mBarState;
-                    mBarState = newState;
-
-                    if (mStatusBarStateController.goingToFullShade()
-                            || mKeyguardStateController.isKeyguardFadingAway()) {
-                        closeSwitcherIfOpenAndNotSimple(true);
-                    }
-
-                    setKeyguardUserSwitcherVisibility(
-                            newState,
-                            keyguardFadingAway,
-                            goingToFullShade,
-                            oldState);
-                }
-
-                @Override
-                public void onDozeAmountChanged(float linearAmount, float amount) {
-                    if (DEBUG) {
-                        Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f",
-                                linearAmount, amount));
-                    }
-                    setDarkAmount(amount);
-                }
-            };
-
-    @Inject
-    public KeyguardUserSwitcherController(
-            KeyguardUserSwitcherView keyguardUserSwitcherView,
-            Context context,
-            @Main Resources resources,
-            LayoutInflater layoutInflater,
-            ScreenLifecycle screenLifecycle,
-            UserSwitcherController userSwitcherController,
-            KeyguardStateController keyguardStateController,
-            SysuiStatusBarStateController statusBarStateController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            DozeParameters dozeParameters,
-            ScreenOffAnimationController screenOffAnimationController) {
-        super(keyguardUserSwitcherView);
-        if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController");
-        mContext = context;
-        mScreenLifecycle = screenLifecycle;
-        mUserSwitcherController = userSwitcherController;
-        mKeyguardStateController = keyguardStateController;
-        mStatusBarStateController = statusBarStateController;
-        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mAdapter = new KeyguardUserAdapter(mContext, resources, layoutInflater,
-                mUserSwitcherController, this);
-        mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
-                keyguardStateController, dozeParameters,
-                screenOffAnimationController, /* animateYPos= */ false,
-                /* logBuffer= */ null);
-        mBackground = new KeyguardUserSwitcherScrim(context);
-    }
-
-    @Override
-    protected void onInit() {
-        super.onInit();
-
-        if (DEBUG) Log.d(TAG, "onInit");
-
-        mListView = mView.findViewById(R.id.keyguard_user_switcher_list);
-
-        mView.setOnTouchListener((v, event) -> {
-            if (!isListAnimating()) {
-                // Hide switcher if it didn't handle the touch event (and block the event from
-                // going through).
-                return closeSwitcherIfOpenAndNotSimple(true);
-            }
-            return false;
-        });
-    }
-
-    @Override
-    protected void onViewAttached() {
-        if (DEBUG) Log.d(TAG, "onViewAttached");
-        mAdapter.registerDataSetObserver(mDataSetObserver);
-        mAdapter.notifyDataSetChanged();
-        mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
-        mScreenLifecycle.addObserver(mScreenObserver);
-        if (isSimpleUserSwitcher()) {
-            // Don't use the background for the simple user switcher
-            setUserSwitcherOpened(true /* open */, true /* animate */);
-        } else {
-            mView.addOnLayoutChangeListener(mBackground);
-            mView.setBackground(mBackground);
-            mBackground.setAlpha(0);
-        }
-    }
-
-    @Override
-    protected void onViewDetached() {
-        if (DEBUG) Log.d(TAG, "onViewDetached");
-
-        // Detaching the view will always close the switcher
-        closeSwitcherIfOpenAndNotSimple(false);
-
-        mAdapter.unregisterDataSetObserver(mDataSetObserver);
-        mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
-        mStatusBarStateController.removeCallback(mStatusBarStateListener);
-        mScreenLifecycle.removeObserver(mScreenObserver);
-        mView.removeOnLayoutChangeListener(mBackground);
-        mView.setBackground(null);
-        mBackground.setAlpha(0);
-    }
-
-    /**
-     * Returns {@code true} if the user switcher should be open by default on the lock screen.
-     *
-     * @see android.os.UserManager#isUserSwitcherEnabled()
-     */
-    public boolean isSimpleUserSwitcher() {
-        return mUserSwitcherController.isSimpleUserSwitcher();
-    }
-
-    public int getHeight() {
-        return mListView.getHeight();
-    }
-
-    /**
-     * @param animate if the transition should be animated
-     * @return true if the switcher state changed
-     */
-    public boolean closeSwitcherIfOpenAndNotSimple(boolean animate) {
-        if (isUserSwitcherOpen() && !isSimpleUserSwitcher()) {
-            setUserSwitcherOpened(false /* open */, animate);
-            return true;
-        }
-        return false;
-    }
-
-    public final DataSetObserver mDataSetObserver = new DataSetObserver() {
-        @Override
-        public void onChanged() {
-            refreshUserList();
-        }
-    };
-
-    void refreshUserList() {
-        final int childCount = mListView.getChildCount();
-        final int adapterCount = mAdapter.getCount();
-        final int count = Math.max(childCount, adapterCount);
-
-        if (DEBUG) {
-            Log.d(TAG, String.format("refreshUserList childCount=%d adapterCount=%d", childCount,
-                    adapterCount));
-        }
-
-        boolean foundCurrentUser = false;
-        for (int i = 0; i < count; i++) {
-            if (i < adapterCount) {
-                View oldView = null;
-                if (i < childCount) {
-                    oldView = mListView.getChildAt(i);
-                }
-                KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView)
-                        mAdapter.getView(i, oldView, mListView);
-                UserRecord userTag =
-                        (UserRecord) newView.getTag();
-                if (userTag.isCurrent) {
-                    if (i != 0) {
-                        Log.w(TAG, "Current user is not the first view in the list");
-                    }
-                    foundCurrentUser = true;
-                    mCurrentUserId = userTag.info.id;
-                    // Current user is always visible
-                    newView.updateVisibilities(true /* showItem */,
-                            mUserSwitcherOpen /* showTextName */, false /* animate */);
-                } else {
-                    // Views for non-current users are always expanded (e.g. they should the name
-                    // next to the user icon). However, they could be hidden entirely if the list
-                    // is closed.
-                    newView.updateVisibilities(mUserSwitcherOpen /* showItem */,
-                            true /* showTextName */, false /* animate */);
-                }
-                newView.setDarkAmount(mDarkAmount);
-                if (oldView == null) {
-                    // We ran out of existing views. Add it at the end.
-                    mListView.addView(newView);
-                } else if (oldView != newView) {
-                    // We couldn't rebind the view. Replace it.
-                    mListView.replaceView(newView, i);
-                }
-            } else {
-                mListView.removeLastView();
-            }
-        }
-        if (!foundCurrentUser) {
-            Log.w(TAG, "Current user is not listed");
-            mCurrentUserId = UserHandle.USER_NULL;
-        }
-    }
-
-    /**
-     * Set the visibility of the keyguard user switcher view based on some new state.
-     */
-    public void setKeyguardUserSwitcherVisibility(
-            int statusBarState,
-            boolean keyguardFadingAway,
-            boolean goingToFullShade,
-            int oldStatusBarState) {
-        mKeyguardVisibilityHelper.setViewVisibility(
-                statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
-    }
-
-    /**
-     * Update position of the view with an optional animation
-     */
-    public void updatePosition(int x, int y, boolean animate) {
-        PropertyAnimator.setProperty(mListView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES,
-                animate);
-        PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
-                ANIMATION_PROPERTIES, animate);
-
-        Rect r = new Rect();
-        mListView.getDrawingRect(r);
-        mView.offsetDescendantRectToMyCoords(mListView, r);
-        mBackground.setGradientCenter(
-                (int) (mListView.getTranslationX() + r.left + r.width() / 2),
-                (int) (mListView.getTranslationY() + r.top + r.height() / 2));
-    }
-
-    /**
-     * Set keyguard user switcher view alpha.
-     */
-    public void setAlpha(float alpha) {
-        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
-            mView.setAlpha(alpha);
-        }
-    }
-
-    /**
-     * Set the amount (ratio) that the device has transitioned to doze.
-     *
-     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
-     */
-    private void setDarkAmount(float darkAmount) {
-        boolean isFullyDozed = darkAmount == 1;
-        if (darkAmount == mDarkAmount) {
-            return;
-        }
-        mDarkAmount = darkAmount;
-        mListView.setDarkAmount(darkAmount);
-        if (isFullyDozed) {
-            closeSwitcherIfOpenAndNotSimple(false);
-        }
-    }
-
-    private boolean isListAnimating() {
-        return mKeyguardVisibilityHelper.isVisibilityAnimating() || mListView.isAnimating();
-    }
-
-    /**
-     * NOTE: switcher state is updated before animations finish.
-     *
-     * @param animate true to animate transition. The user switcher state (i.e.
-     *                {@link #isUserSwitcherOpen()}) is updated before animation is finished.
-     */
-    private void setUserSwitcherOpened(boolean open, boolean animate) {
-        if (DEBUG) {
-            Log.d(TAG,
-                    String.format("setUserSwitcherOpened: %b -> %b (animate=%b)",
-                            mUserSwitcherOpen, open, animate));
-        }
-        mUserSwitcherOpen = open;
-        updateVisibilities(animate);
-    }
-
-    private void updateVisibilities(boolean animate) {
-        if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate));
-        if (mBgAnimator != null) {
-            mBgAnimator.cancel();
-        }
-
-        if (mUserSwitcherOpen) {
-            mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
-            mBgAnimator.setDuration(400);
-            mBgAnimator.setInterpolator(Interpolators.ALPHA_IN);
-            mBgAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mBgAnimator = null;
-                }
-            });
-            mBgAnimator.start();
-        } else {
-            mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 255, 0);
-            mBgAnimator.setDuration(400);
-            mBgAnimator.setInterpolator(Interpolators.ALPHA_OUT);
-            mBgAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mBgAnimator = null;
-                }
-            });
-            mBgAnimator.start();
-        }
-        mListView.updateVisibilities(mUserSwitcherOpen, animate);
-    }
-
-    private boolean isUserSwitcherOpen() {
-        return mUserSwitcherOpen;
-    }
-
-    static class KeyguardUserAdapter extends
-            BaseUserSwitcherAdapter implements View.OnClickListener {
-
-        private final Context mContext;
-        private final Resources mResources;
-        private final LayoutInflater mLayoutInflater;
-        private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
-        private View mCurrentUserView;
-        // List of users where the first entry is always the current user
-        private ArrayList<UserRecord> mUsersOrdered = new ArrayList<>();
-
-        KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater,
-                UserSwitcherController controller,
-                KeyguardUserSwitcherController keyguardUserSwitcherController) {
-            super(controller);
-            mContext = context;
-            mResources = resources;
-            mLayoutInflater = layoutInflater;
-            mKeyguardUserSwitcherController = keyguardUserSwitcherController;
-        }
-
-        @Override
-        public void notifyDataSetChanged() {
-            // At this point, value of isSimpleUserSwitcher() may have changed in addition to the
-            // data set
-            refreshUserOrder();
-            super.notifyDataSetChanged();
-        }
-
-        void refreshUserOrder() {
-            List<UserRecord> users = super.getUsers();
-            mUsersOrdered = new ArrayList<>(users.size());
-            for (int i = 0; i < users.size(); i++) {
-                UserRecord record = users.get(i);
-                if (record.isCurrent) {
-                    mUsersOrdered.add(0, record);
-                } else {
-                    mUsersOrdered.add(record);
-                }
-            }
-        }
-
-        @Override
-        protected ArrayList<UserRecord> getUsers() {
-            return mUsersOrdered;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            UserRecord item = getItem(position);
-            return createUserDetailItemView(convertView, parent, item);
-        }
-
-        KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
-            if (!(convertView instanceof KeyguardUserDetailItemView)
-                    || !(convertView.getTag() instanceof UserRecord)) {
-                convertView = mLayoutInflater.inflate(
-                        R.layout.keyguard_user_switcher_item, parent, false);
-            }
-            return (KeyguardUserDetailItemView) convertView;
-        }
-
-        KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
-                UserRecord item) {
-            KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
-            v.setOnClickListener(this);
-
-            String name = getName(mContext, item);
-            if (item.picture == null) {
-                v.bind(name, getDrawable(item).mutate(), item.resolveId());
-            } else {
-                int avatarSize =
-                        (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
-                Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
-                drawable.setColorFilter(
-                        item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
-                v.bind(name, drawable, item.info.id);
-            }
-            v.setActivated(item.isCurrent);
-            v.setDisabledByAdmin(item.isDisabledByAdmin());
-            v.setEnabled(item.isSwitchToEnabled);
-            UserSwitcherController.setSelectableAlpha(v);
-
-            if (item.isCurrent) {
-                mCurrentUserView = v;
-            }
-            v.setTag(item);
-            return v;
-        }
-
-        private Drawable getDrawable(UserRecord item) {
-            Drawable drawable;
-            if (item.isCurrent && item.isGuest) {
-                drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
-            } else {
-                drawable = getIconDrawable(mContext, item);
-            }
-
-            int iconColorRes;
-            if (item.isSwitchToEnabled) {
-                iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
-            } else {
-                iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
-            }
-            drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
-
-            Drawable bg = mContext.getDrawable(com.android.settingslib.R.drawable.user_avatar_bg);
-            drawable = new LayerDrawable(new Drawable[]{bg, drawable});
-            return drawable;
-        }
-
-        @Override
-        public void onClick(View v) {
-            UserRecord user = (UserRecord) v.getTag();
-
-            if (mKeyguardUserSwitcherController.isListAnimating()) {
-                return;
-            }
-
-            if (mKeyguardUserSwitcherController.isUserSwitcherOpen()) {
-                if (!user.isCurrent || user.isGuest) {
-                    onUserListItemClicked(user);
-                } else {
-                    mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(
-                            true /* animate */);
-                }
-            } else {
-                // If switcher is closed, tapping anywhere in the view will open it
-                mKeyguardUserSwitcherController.setUserSwitcherOpened(
-                        true /* open */, true /* animate */);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
deleted file mode 100644
index 363b06a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2021 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.policy;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-
-import com.android.app.animation.Interpolators;
-import com.android.keyguard.AlphaOptimizedLinearLayout;
-import com.android.keyguard.KeyguardConstants;
-import com.android.settingslib.animation.AppearAnimationUtils;
-import com.android.settingslib.animation.DisappearAnimationUtils;
-
-/**
- * The container for the user switcher on Keyguard.
- */
-public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout {
-
-    private static final String TAG = "KeyguardUserSwitcherListView";
-    private static final boolean DEBUG = KeyguardConstants.DEBUG;
-
-    private boolean mAnimating;
-    private final AppearAnimationUtils mAppearAnimationUtils;
-    private final DisappearAnimationUtils mDisappearAnimationUtils;
-
-    public KeyguardUserSwitcherListView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mAppearAnimationUtils = new AppearAnimationUtils(context,
-                AppearAnimationUtils.DEFAULT_APPEAR_DURATION,
-                -0.5f /* translationScaleFactor */,
-                0.5f /* delayScaleFactor */,
-                Interpolators.FAST_OUT_SLOW_IN);
-        mDisappearAnimationUtils = new DisappearAnimationUtils(context,
-                AppearAnimationUtils.DEFAULT_APPEAR_DURATION,
-                0.2f /* translationScaleFactor */,
-                0.2f /* delayScaleFactor */,
-                Interpolators.FAST_OUT_SLOW_IN_REVERSE);
-    }
-
-    /**
-     * Set the amount (ratio) that the device has transitioned to doze.
-     *
-     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
-     */
-    void setDarkAmount(float darkAmount) {
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View v = getChildAt(i);
-            if (v instanceof KeyguardUserDetailItemView) {
-                ((KeyguardUserDetailItemView) v).setDarkAmount(darkAmount);
-            }
-        }
-    }
-
-    boolean isAnimating() {
-        return mAnimating;
-    }
-
-    /**
-     * Update visibilities of this view and child views for when the user list is open or closed.
-     * If closed, this hides everything but the first item (which is always the current user).
-     */
-    void updateVisibilities(boolean open, boolean animate) {
-        if (DEBUG) {
-            Log.d(TAG, String.format("updateVisibilities: open=%b animate=%b childCount=%d",
-                    open, animate, getChildCount()));
-        }
-
-        mAnimating = false;
-
-        int childCount = getChildCount();
-        KeyguardUserDetailItemView[] userItemViews = new KeyguardUserDetailItemView[childCount];
-        for (int i = 0; i < childCount; i++) {
-            userItemViews[i] = (KeyguardUserDetailItemView) getChildAt(i);
-            userItemViews[i].clearAnimation();
-            if (i == 0) {
-                // The first child is always the current user.
-                userItemViews[i].updateVisibilities(true /* showItem */, open /* showTextName */,
-                        animate);
-                userItemViews[i].setClickable(true);
-            } else {
-                // Update clickable state immediately so that the menu feels more responsive
-                userItemViews[i].setClickable(open);
-                // when opening we need to make views visible beforehand so they can be animated
-                if (open) {
-                    userItemViews[i].updateVisibilities(true /* showItem */,
-                            true /* showTextName */, false /* animate */);
-                }
-
-            }
-        }
-
-        if (animate && userItemViews.length > 1) {
-            // AnimationUtils will immediately hide/show the first item in the array. Since the
-            // first view is the current user, we want to manage its visibility separately.
-            // Set first item to null so AnimationUtils ignores it.
-            userItemViews[0] = null;
-
-            setClipChildren(false);
-            setClipToPadding(false);
-            mAnimating = true;
-            (open ? mAppearAnimationUtils : mDisappearAnimationUtils)
-                    .startAnimation(userItemViews, () -> {
-                        setClipChildren(true);
-                        setClipToPadding(true);
-                        mAnimating = false;
-                        if (!open) {
-                            // after closing we hide children so that height of this view is correct
-                            for (int i = 1; i < userItemViews.length; i++) {
-                                userItemViews[i].updateVisibilities(false /* showItem */,
-                                        true /* showTextName */, false /* animate */);
-                            }
-                        }
-                    });
-        }
-    }
-
-    /**
-     * Replaces the view at the specified position in the group.
-     *
-     * @param index the position in the group of the view to remove
-     */
-    void replaceView(KeyguardUserDetailItemView newView, int index) {
-        removeViewAt(index);
-        addView(newView, index);
-    }
-
-    /**
-     * Removes the last view in the group.
-     */
-    void removeLastView() {
-        int lastIndex = getChildCount() - 1;
-        removeViewAt(lastIndex);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
deleted file mode 100644
index 5ed207cc3..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherScrim.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2014 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.policy;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.RadialGradient;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-
-import com.android.systemui.res.R;
-
-/**
- * Gradient background for the user switcher on Keyguard.
- */
-public class KeyguardUserSwitcherScrim extends Drawable
-        implements View.OnLayoutChangeListener {
-
-    private static final float OUTER_EXTENT = 2.5f;
-    private static final float INNER_EXTENT = 0.25f;
-
-    private int mDarkColor;
-    private int mAlpha = 255;
-    private Paint mRadialGradientPaint = new Paint();
-    private int mCircleX;
-    private int mCircleY;
-    private int mSize;
-
-    public KeyguardUserSwitcherScrim(Context context) {
-        mDarkColor = context.getColor(
-                R.color.keyguard_user_switcher_background_gradient_color);
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        if (mAlpha == 0) {
-            return;
-        }
-        Rect bounds = getBounds();
-        canvas.drawRect(bounds.left, bounds.top, bounds.right, bounds.bottom, mRadialGradientPaint);
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mAlpha = alpha;
-        updatePaint();
-        invalidateSelf();
-    }
-
-    @Override
-    public int getAlpha() {
-        return mAlpha;
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) {
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    @Override
-    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
-            int oldTop, int oldRight, int oldBottom) {
-        if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
-            int width = right - left;
-            int height = bottom - top;
-            mSize = Math.max(width, height);
-            updatePaint();
-        }
-    }
-
-    private void updatePaint() {
-        if (mSize == 0) {
-            return;
-        }
-        float outerRadius = mSize * OUTER_EXTENT;
-        mRadialGradientPaint.setShader(
-                new RadialGradient(mCircleX, mCircleY, outerRadius,
-                        new int[] { Color.argb(
-                                        (int) (Color.alpha(mDarkColor) * mAlpha / 255f), 0, 0, 0),
-                                Color.TRANSPARENT },
-                        new float[] { Math.max(0f, INNER_EXTENT / OUTER_EXTENT), 1f },
-                        Shader.TileMode.CLAMP));
-    }
-
-    /**
-     * Sets the center of the radial gradient used as a background
-     *
-     * @param x
-     * @param y
-     */
-    public void setGradientCenter(int x, int y) {
-        mCircleX = x;
-        mCircleY = y;
-        updatePaint();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
deleted file mode 100644
index 3f0e23f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2021 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.policy;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-/**
- * The container for the user switcher on Keyguard.
- */
-public class KeyguardUserSwitcherView extends FrameLayout {
-
-    public KeyguardUserSwitcherView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index f04fb2c..1ae5682 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -24,16 +24,16 @@
 import androidx.annotation.LayoutRes
 import androidx.compose.ui.util.fastForEachIndexed
 import androidx.constraintlayout.motion.widget.MotionLayout
-import androidx.constraintlayout.widget.ConstraintSet
 import androidx.dynamicanimation.animation.DynamicAnimation
 import androidx.dynamicanimation.animation.FloatValueHolder
 import androidx.dynamicanimation.animation.SpringAnimation
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.internal.R as internalR
 import com.android.systemui.res.R
-import com.android.systemui.util.children
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.ringer.ui.util.VolumeDialogRingerDrawerTransitionListener
+import com.android.systemui.volume.dialog.ringer.ui.util.updateCloseState
+import com.android.systemui.volume.dialog.ringer.ui.util.updateOpenState
 import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerButtonUiModel
 import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerButtonViewModel
 import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerDrawerState
@@ -93,6 +93,7 @@
         }
         drawerContainer.setTransitionListener(ringerDrawerTransitionListener)
         volumeDialogBackgroundView.background = volumeDialogBackgroundView.background.mutate()
+
         viewModel.ringerViewModel
             .onEach { ringerState ->
                 when (ringerState) {
@@ -110,7 +111,10 @@
                                     unselectedButtonUiModel,
                                 )
                                 ringerDrawerTransitionListener.setProgressChangeEnabled(true)
-                                drawerContainer.closeDrawer(uiModel.currentButtonIndex)
+                                drawerContainer.closeDrawer(
+                                    uiModel.currentButtonIndex,
+                                    ringerState.orientation,
+                                )
                             }
 
                             is RingerDrawerState.Closed -> {
@@ -125,8 +129,10 @@
                                         unselectedButtonUiModel,
                                         onProgressChanged = { progress, isReverse ->
                                             // Let's make button progress when switching matches
-                                            // motionLayout transition progress. When full radius,
-                                            // progress is 0.0. When small radius, progress is 1.0.
+                                            // motionLayout transition progress. When full
+                                            // radius,
+                                            // progress is 0.0. When small radius, progress is
+                                            // 1.0.
                                             backgroundAnimationProgress =
                                                 if (isReverse) {
                                                     1F - progress
@@ -147,7 +153,10 @@
                                                 true
                                             )
                                         }
-                                        drawerContainer.closeDrawer(uiModel.currentButtonIndex)
+                                        drawerContainer.closeDrawer(
+                                            uiModel.currentButtonIndex,
+                                            ringerState.orientation,
+                                        )
                                     }
                                 }
                             }
@@ -167,6 +176,7 @@
                                 } else {
                                     ringerDrawerTransitionListener.setProgressChangeEnabled(true)
                                 }
+                                updateOpenState(drawerContainer, ringerState.orientation)
                                 drawerContainer.transitionToState(
                                     R.id.volume_dialog_ringer_drawer_open
                                 )
@@ -223,23 +233,30 @@
             // We only need to execute on roundness animation end and volume dialog background
             // progress update once because these changes should be applied once on volume dialog
             // background and ringer drawer views.
-            selectedButton.animateTo(
-                selectedButtonUiModel,
-                if (uiModel.currentButtonIndex == count - 1) {
-                    onProgressChanged
-                } else {
-                    { _, _ -> }
-                },
-                roundnessAnimationEndListener,
-            )
-            unselectedButton.animateTo(
-                unselectedButtonUiModel,
-                if (previousIndex == count - 1) {
-                    onProgressChanged
-                } else {
-                    { _, _ -> }
-                },
-            )
+            val selectedCornerRadius = (selectedButton.background as GradientDrawable).cornerRadius
+            if (selectedCornerRadius.toInt() != selectedButtonUiModel.cornerRadius) {
+                selectedButton.animateTo(
+                    selectedButtonUiModel,
+                    if (uiModel.currentButtonIndex == count - 1) {
+                        onProgressChanged
+                    } else {
+                        { _, _ -> }
+                    },
+                    roundnessAnimationEndListener,
+                )
+            }
+            val unselectedCornerRadius =
+                (unselectedButton.background as GradientDrawable).cornerRadius
+            if (unselectedCornerRadius.toInt() != unselectedButtonUiModel.cornerRadius) {
+                unselectedButton.animateTo(
+                    unselectedButtonUiModel,
+                    if (previousIndex == count - 1) {
+                        onProgressChanged
+                    } else {
+                        { _, _ -> }
+                    },
+                )
+            }
         } else {
             bindButtons(viewModel, uiModel, onAnimationEnd)
         }
@@ -318,107 +335,16 @@
                     inflater.inflate(viewLayoutId, this, true)
                     getChildAt(childCount - 1).id = View.generateViewId()
                 }
-                cloneConstraintSet(R.id.volume_dialog_ringer_drawer_open)
-                    .adjustOpenConstraintsForDrawer(this)
             }
         }
     }
 
-    private fun MotionLayout.closeDrawer(selectedIndex: Int) {
+    private fun MotionLayout.closeDrawer(selectedIndex: Int, orientation: Int) {
         setTransition(R.id.close_to_open_transition)
-        cloneConstraintSet(R.id.volume_dialog_ringer_drawer_close)
-            .adjustClosedConstraintsForDrawer(selectedIndex, this)
+        updateCloseState(this, selectedIndex, orientation)
         transitionToState(R.id.volume_dialog_ringer_drawer_close)
     }
 
-    private fun ConstraintSet.adjustOpenConstraintsForDrawer(motionLayout: MotionLayout) {
-        motionLayout.children.forEachIndexed { index, button ->
-            setButtonPositionConstraints(motionLayout, index, button)
-            setAlpha(button.id, 1.0F)
-            constrainWidth(
-                button.id,
-                motionLayout.context.resources.getDimensionPixelSize(
-                    R.dimen.volume_dialog_ringer_drawer_button_size
-                ),
-            )
-            constrainHeight(
-                button.id,
-                motionLayout.context.resources.getDimensionPixelSize(
-                    R.dimen.volume_dialog_ringer_drawer_button_size
-                ),
-            )
-            if (index != motionLayout.childCount - 1) {
-                setMargin(
-                    button.id,
-                    ConstraintSet.BOTTOM,
-                    motionLayout.context.resources.getDimensionPixelSize(
-                        R.dimen.volume_dialog_components_spacing
-                    ),
-                )
-            }
-        }
-        motionLayout.updateState(R.id.volume_dialog_ringer_drawer_open, this)
-    }
-
-    private fun ConstraintSet.adjustClosedConstraintsForDrawer(
-        selectedIndex: Int,
-        motionLayout: MotionLayout,
-    ) {
-        motionLayout.children.forEachIndexed { index, button ->
-            setButtonPositionConstraints(motionLayout, index, button)
-            constrainWidth(
-                button.id,
-                motionLayout.context.resources.getDimensionPixelSize(
-                    R.dimen.volume_dialog_ringer_drawer_button_size
-                ),
-            )
-            if (selectedIndex != motionLayout.childCount - index - 1) {
-                setAlpha(button.id, 0.0F)
-                constrainHeight(button.id, 0)
-                setMargin(button.id, ConstraintSet.BOTTOM, 0)
-            } else {
-                setAlpha(button.id, 1.0F)
-                constrainHeight(
-                    button.id,
-                    motionLayout.context.resources.getDimensionPixelSize(
-                        R.dimen.volume_dialog_ringer_drawer_button_size
-                    ),
-                )
-            }
-        }
-        motionLayout.updateState(R.id.volume_dialog_ringer_drawer_close, this)
-    }
-
-    private fun ConstraintSet.setButtonPositionConstraints(
-        motionLayout: MotionLayout,
-        index: Int,
-        button: View,
-    ) {
-        if (motionLayout.getChildAt(index - 1) == null) {
-            connect(button.id, ConstraintSet.TOP, motionLayout.id, ConstraintSet.TOP)
-        } else {
-            connect(
-                button.id,
-                ConstraintSet.TOP,
-                motionLayout.getChildAt(index - 1).id,
-                ConstraintSet.BOTTOM,
-            )
-        }
-
-        if (motionLayout.getChildAt(index + 1) == null) {
-            connect(button.id, ConstraintSet.BOTTOM, motionLayout.id, ConstraintSet.BOTTOM)
-        } else {
-            connect(
-                button.id,
-                ConstraintSet.BOTTOM,
-                motionLayout.getChildAt(index + 1).id,
-                ConstraintSet.TOP,
-            )
-        }
-        connect(button.id, ConstraintSet.START, motionLayout.id, ConstraintSet.START)
-        connect(button.id, ConstraintSet.END, motionLayout.id, ConstraintSet.END)
-    }
-
     private suspend fun ImageButton.animateTo(
         ringerButtonUiModel: RingerButtonUiModel,
         onProgressChanged: (Float, Boolean) -> Unit = { _, _ -> },
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt
new file mode 100644
index 0000000..25ba1bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.ui.util
+
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.view.View
+import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.res.R
+import com.android.systemui.util.children
+
+fun updateOpenState(ringerDrawer: MotionLayout, orientation: Int) {
+    val openSet = ringerDrawer.cloneConstraintSet(R.id.volume_dialog_ringer_drawer_open)
+    openSet.adjustOpenConstraintsForDrawer(ringerDrawer, orientation)
+    ringerDrawer.updateState(R.id.volume_dialog_ringer_drawer_open, openSet)
+}
+
+fun updateCloseState(ringerDrawer: MotionLayout, selectedIndex: Int, orientation: Int) {
+    val closeSet = ringerDrawer.cloneConstraintSet(R.id.volume_dialog_ringer_drawer_close)
+    closeSet.adjustClosedConstraintsForDrawer(ringerDrawer, selectedIndex, orientation)
+    ringerDrawer.updateState(R.id.volume_dialog_ringer_drawer_close, closeSet)
+}
+
+private fun ConstraintSet.setButtonPositionPortraitConstraints(
+    motionLayout: MotionLayout,
+    index: Int,
+    button: View,
+) {
+    if (motionLayout.getChildAt(index - 1) == null) {
+        connect(button.id, ConstraintSet.TOP, motionLayout.id, ConstraintSet.TOP)
+    } else {
+        connect(
+            button.id,
+            ConstraintSet.TOP,
+            motionLayout.getChildAt(index - 1).id,
+            ConstraintSet.BOTTOM,
+        )
+    }
+
+    if (motionLayout.getChildAt(index + 1) == null) {
+        connect(button.id, ConstraintSet.BOTTOM, motionLayout.id, ConstraintSet.BOTTOM)
+    } else {
+        connect(
+            button.id,
+            ConstraintSet.BOTTOM,
+            motionLayout.getChildAt(index + 1).id,
+            ConstraintSet.TOP,
+        )
+    }
+    connect(button.id, ConstraintSet.START, motionLayout.id, ConstraintSet.START)
+    connect(button.id, ConstraintSet.END, motionLayout.id, ConstraintSet.END)
+    clear(button.id, ConstraintSet.LEFT)
+    clear(button.id, ConstraintSet.RIGHT)
+}
+
+private fun ConstraintSet.setButtonPositionLandscapeConstraints(
+    motionLayout: MotionLayout,
+    index: Int,
+    button: View,
+) {
+    if (motionLayout.getChildAt(index - 1) == null) {
+        connect(button.id, ConstraintSet.LEFT, motionLayout.id, ConstraintSet.LEFT)
+    } else {
+        connect(
+            button.id,
+            ConstraintSet.LEFT,
+            motionLayout.getChildAt(index - 1).id,
+            ConstraintSet.RIGHT,
+        )
+    }
+    if (motionLayout.getChildAt(index + 1) == null) {
+        connect(button.id, ConstraintSet.RIGHT, motionLayout.id, ConstraintSet.RIGHT)
+    } else {
+        connect(
+            button.id,
+            ConstraintSet.RIGHT,
+            motionLayout.getChildAt(index + 1).id,
+            ConstraintSet.LEFT,
+        )
+    }
+    connect(button.id, ConstraintSet.TOP, motionLayout.id, ConstraintSet.TOP)
+    connect(button.id, ConstraintSet.BOTTOM, motionLayout.id, ConstraintSet.BOTTOM)
+    clear(button.id, ConstraintSet.START)
+    clear(button.id, ConstraintSet.END)
+}
+
+private fun ConstraintSet.adjustOpenConstraintsForDrawer(
+    motionLayout: MotionLayout,
+    lastOrientation: Int,
+) {
+    motionLayout.children.forEachIndexed { index, button ->
+        setAlpha(button.id, 1.0F)
+        constrainWidth(
+            button.id,
+            motionLayout.context.resources.getDimensionPixelSize(
+                R.dimen.volume_dialog_ringer_drawer_button_size
+            ),
+        )
+        constrainHeight(
+            button.id,
+            motionLayout.context.resources.getDimensionPixelSize(
+                R.dimen.volume_dialog_ringer_drawer_button_size
+            ),
+        )
+        when (lastOrientation) {
+            ORIENTATION_LANDSCAPE -> {
+                setButtonPositionLandscapeConstraints(motionLayout, index, button)
+                if (index != motionLayout.childCount - 1) {
+                    setMargin(
+                        button.id,
+                        ConstraintSet.RIGHT,
+                        motionLayout.context.resources.getDimensionPixelSize(
+                            R.dimen.volume_dialog_components_spacing
+                        ),
+                    )
+                } else {
+                    setMargin(button.id, ConstraintSet.RIGHT, 0)
+                }
+                setMargin(button.id, ConstraintSet.BOTTOM, 0)
+            }
+            ORIENTATION_PORTRAIT -> {
+                setButtonPositionPortraitConstraints(motionLayout, index, button)
+                if (index != motionLayout.childCount - 1) {
+                    setMargin(
+                        button.id,
+                        ConstraintSet.BOTTOM,
+                        motionLayout.context.resources.getDimensionPixelSize(
+                            R.dimen.volume_dialog_components_spacing
+                        ),
+                    )
+                } else {
+                    setMargin(button.id, ConstraintSet.BOTTOM, 0)
+                }
+                setMargin(button.id, ConstraintSet.RIGHT, 0)
+            }
+        }
+    }
+}
+
+private fun ConstraintSet.adjustClosedConstraintsForDrawer(
+    motionLayout: MotionLayout,
+    selectedIndex: Int,
+    lastOrientation: Int,
+) {
+    motionLayout.children.forEachIndexed { index, button ->
+        setMargin(button.id, ConstraintSet.RIGHT, 0)
+        setMargin(button.id, ConstraintSet.BOTTOM, 0)
+        when (lastOrientation) {
+            ORIENTATION_LANDSCAPE -> {
+                setButtonPositionLandscapeConstraints(motionLayout, index, button)
+                if (selectedIndex != motionLayout.childCount - index - 1) {
+                    setAlpha(button.id, 0.0F)
+                    constrainWidth(button.id, 0)
+                } else {
+                    setAlpha(button.id, 1.0F)
+                    constrainWidth(
+                        button.id,
+                        motionLayout.context.resources.getDimensionPixelSize(
+                            R.dimen.volume_dialog_ringer_drawer_button_size
+                        ),
+                    )
+                }
+                constrainHeight(
+                    button.id,
+                    motionLayout.context.resources.getDimensionPixelSize(
+                        R.dimen.volume_dialog_ringer_drawer_button_size
+                    ),
+                )
+            }
+            ORIENTATION_PORTRAIT -> {
+                setButtonPositionPortraitConstraints(motionLayout, index, button)
+                if (selectedIndex != motionLayout.childCount - index - 1) {
+                    setAlpha(button.id, 0.0F)
+                    constrainHeight(button.id, 0)
+                } else {
+                    setAlpha(button.id, 1.0F)
+                    constrainHeight(
+                        button.id,
+                        motionLayout.context.resources.getDimensionPixelSize(
+                            R.dimen.volume_dialog_ringer_drawer_button_size
+                        ),
+                    )
+                }
+                constrainWidth(
+                    button.id,
+                    motionLayout.context.resources.getDimensionPixelSize(
+                        R.dimen.volume_dialog_ringer_drawer_button_size
+                    ),
+                )
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModelState.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModelState.kt
index 78b00af..50898b6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModelState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModelState.kt
@@ -19,7 +19,8 @@
 /** Models ringer view model state. */
 sealed class RingerViewModelState {
 
-    data class Available(val uiModel: RingerViewModel) : RingerViewModelState()
+    data class Available(val uiModel: RingerViewModel, val orientation: Int) :
+        RingerViewModelState()
 
     data object Unavailable : RingerViewModelState()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
index 627d75e..eec64d9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.onConfigChanged
 import com.android.systemui.volume.Events
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
@@ -48,6 +50,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
@@ -65,19 +68,29 @@
     private val vibrator: VibratorHelper,
     private val volumeDialogLogger: VolumeDialogLogger,
     private val visibilityInteractor: VolumeDialogVisibilityInteractor,
+    configurationController: ConfigurationController,
 ) {
 
     private val drawerState = MutableStateFlow<RingerDrawerState>(RingerDrawerState.Initial)
+    private val orientation: StateFlow<Int> =
+        configurationController.onConfigChanged
+            .map { it.orientation }
+            .stateIn(
+                coroutineScope,
+                SharingStarted.Eagerly,
+                applicationContext.resources.configuration.orientation,
+            )
 
     val ringerViewModel: StateFlow<RingerViewModelState> =
         combine(
                 soundPolicyInteractor.isZenMuted(AudioStream(STREAM_RING)),
                 ringerInteractor.ringerModel,
                 drawerState,
-            ) { isZenMuted, ringerModel, state ->
+                orientation,
+            ) { isZenMuted, ringerModel, state, orientation ->
                 level = ringerModel.level
                 levelMax = ringerModel.levelMax
-                ringerModel.toViewModel(state, isZenMuted)
+                ringerModel.toViewModel(state, isZenMuted, orientation)
             }
             .flowOn(backgroundDispatcher)
             .stateIn(coroutineScope, SharingStarted.Eagerly, RingerViewModelState.Unavailable)
@@ -133,6 +146,7 @@
     private fun VolumeDialogRingerModel.toViewModel(
         drawerState: RingerDrawerState,
         isZenMuted: Boolean,
+        orientation: Int,
     ): RingerViewModelState {
         val currentIndex = availableModes.indexOf(currentRingerMode)
         if (currentIndex == -1) {
@@ -149,7 +163,8 @@
                         currentButtonIndex = currentIndex,
                         selectedButton = it,
                         drawerState = drawerState,
-                    )
+                    ),
+                    orientation,
                 )
             } ?: RingerViewModelState.Unavailable
         }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
index c0c525b..88af210 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.dialog.sliders.dagger
 
 import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder
 import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder
 import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderTouchesViewBinder
 import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
@@ -37,6 +38,8 @@
 
     fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder
 
+    fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder
+
     @Subcomponent.Factory
     interface Factory {
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt
index adc2383..82885d6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.volume.dialog.sliders.data.repository
 
-import android.annotation.SuppressLint
 import android.view.MotionEvent
 import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
 import javax.inject.Inject
@@ -27,7 +26,6 @@
 @VolumeDialogSliderScope
 class VolumeDialogSliderTouchEventsRepository @Inject constructor() {
 
-    @SuppressLint("SharedFlowCreation")
     private val mutableSliderTouchEvents: MutableStateFlow<MotionEvent?> = MutableStateFlow(null)
     val sliderTouchEvent: Flow<MotionEvent> = mutableSliderTouchEvents.filterNotNull()
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
index 2967fe8..04dc80c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
@@ -17,14 +17,18 @@
 package com.android.systemui.volume.dialog.sliders.domain.interactor
 
 import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
 import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
 import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
 
 /** Operates a state of particular slider of the Volume Dialog. */
 @VolumeDialogSliderScope
@@ -32,6 +36,7 @@
 @Inject
 constructor(
     private val sliderType: VolumeDialogSliderType,
+    @VolumeDialog private val coroutineScope: CoroutineScope,
     volumeDialogStateInteractor: VolumeDialogStateInteractor,
     private val volumeDialogController: VolumeDialogController,
 ) {
@@ -47,7 +52,8 @@
                     }
                 }
             }
-            .distinctUntilChanged()
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+            .filterNotNull()
 
     fun setStreamVolume(userLevel: Int) {
         with(volumeDialogController) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt
index c904ac5..690f9ef 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt
@@ -63,7 +63,7 @@
                 LinkedHashSet(sliderTypes)
             }
             .runningReduce { sliderTypes, newSliderTypes ->
-                newSliderTypes.apply { addAll(sliderTypes) }
+                sliderTypes.apply { addAll(newSliderTypes) }
             }
             .map { sliderTypes ->
                 VolumeDialogSlidersModel(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt
new file mode 100644
index 0000000..8109b50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui
+
+import android.view.View
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel.OverscrollEventModel
+import com.google.android.material.slider.Slider
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@VolumeDialogSliderScope
+class VolumeDialogOverscrollViewBinder
+@Inject
+constructor(private val viewModel: VolumeDialogOverscrollViewModel) {
+
+    /**
+     * [viewsToAnimate] is an array of [View] to be affected by the overscroll animation. [view] is
+     * NOT animated by default.
+     */
+    fun CoroutineScope.bind(view: View, viewsToAnimate: Array<View>) {
+        val animationValueHolder = FloatValueHolder(0f)
+        val animation: SpringAnimation =
+            SpringAnimation(animationValueHolder)
+                .setSpring(
+                    SpringForce(0f).apply {
+                        stiffness = 800f
+                        dampingRatio = 0.6f
+                    }
+                )
+                .addUpdateListener { _, value, _ -> viewsToAnimate.setTranslationY(value) }
+
+        view.requireViewById<Slider>(R.id.volume_dialog_slider).addOnChangeListener { s, value, _ ->
+            viewModel.setSlider(value = value, min = s.valueFrom, max = s.valueTo)
+        }
+
+        viewModel.overscrollEvent
+            .onEach { event ->
+                when (event) {
+                    is OverscrollEventModel.Animate -> {
+                        animation.animateToFinalPosition(event.targetOffsetPx)
+                    }
+                    is OverscrollEventModel.Move -> {
+                        animation.cancel()
+                        viewsToAnimate.setTranslationY(event.touchOffsetPx)
+                        animationValueHolder.value = event.touchOffsetPx
+                    }
+                }
+            }
+            .launchIn(this)
+    }
+}
+
+private fun Array<View>.setTranslationY(translation: Float) {
+    for (viewToAnimate in this) {
+        viewToAnimate.translationY = translation
+    }
+}
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 faf06b94..a7ffcd7 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
@@ -55,22 +55,21 @@
             viewModel.setStreamVolume(value.roundToInt(), fromUser)
         }
 
-        viewModel.state.onEach { it.bindToSlider(sliderView) }.launchIn(this)
+        viewModel.state.onEach { sliderView.setModel(it) }.launchIn(this)
     }
 
     @SuppressLint("UseCompatLoadingForDrawables")
-    private suspend fun VolumeDialogSliderStateModel.bindToSlider(slider: Slider) {
-        with(slider) {
-            valueFrom = minValue
-            valueTo = maxValue
-            // coerce the current value to the new value range before animating it
-            value = value.coerceIn(valueFrom, valueTo)
-            setValueAnimated(
-                value,
-                jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS),
-            )
-            trackIconActiveEnd = context.getDrawable(iconRes)
-        }
+    private suspend fun Slider.setModel(model: VolumeDialogSliderStateModel) {
+        valueFrom = model.minValue
+        valueTo = model.maxValue
+        // coerce the current value to the new value range before animating it. This prevents
+        // animating from the value that is outside of current [valueFrom, valueTo].
+        value = value.coerceIn(valueFrom, valueTo)
+        setValueAnimated(
+            model.value,
+            jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS),
+        )
+        trackIconActiveEnd = context.getDrawable(model.iconRes)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index c9b5259..f066b56 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -40,9 +40,17 @@
             view.requireViewById(R.id.volume_dialog_floating_sliders_container)
         val mainSliderContainer: View =
             view.requireViewById(R.id.volume_dialog_main_slider_container)
+        val background: View = view.requireViewById(R.id.volume_dialog_background)
+        val settingsButton: View = view.requireViewById(R.id.volume_dialog_settings)
+        val ringerDrawer: View = view.requireViewById(R.id.volume_ringer_drawer)
+
         viewModel.sliders
             .onEach { uiModel ->
-                bindSlider(uiModel.sliderComponent, mainSliderContainer)
+                bindSlider(
+                    uiModel.sliderComponent,
+                    mainSliderContainer,
+                    arrayOf(mainSliderContainer, background, settingsButton, ringerDrawer),
+                )
 
                 val floatingSliderViewBinders = uiModel.floatingSliderComponent
                 floatingSlidersContainer.ensureChildCount(
@@ -50,7 +58,8 @@
                     count = floatingSliderViewBinders.size,
                 )
                 floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent ->
-                    bindSlider(sliderComponent, floatingSlidersContainer.getChildAt(index))
+                    val sliderContainer = floatingSlidersContainer.getChildAt(index)
+                    bindSlider(sliderComponent, sliderContainer, arrayOf(sliderContainer))
                 }
             }
             .launchIn(this)
@@ -59,10 +68,12 @@
     private fun CoroutineScope.bindSlider(
         component: VolumeDialogSliderComponent,
         sliderContainer: View,
+        viewsToAnimate: Array<View>,
     ) {
         with(component.sliderViewBinder()) { bind(sliderContainer) }
         with(component.sliderTouchesViewBinder()) { bind(sliderContainer) }
         with(component.sliderHapticsViewBinder()) { bind(sliderContainer) }
+        with(component.overscrollViewBinder()) { bind(sliderContainer, viewsToAnimate) }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt
new file mode 100644
index 0000000..0d41860
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui.viewmodel
+
+import android.content.Context
+import android.view.MotionEvent
+import android.view.animation.PathInterpolator
+import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInputEventsInteractor
+import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent
+import javax.inject.Inject
+import kotlin.math.abs
+import kotlin.math.sign
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.transform
+
+@VolumeDialogSliderScope
+@OptIn(ExperimentalCoroutinesApi::class)
+class VolumeDialogOverscrollViewModel
+@Inject
+constructor(
+    context: Context,
+    private val inputEventsInteractor: VolumeDialogSliderInputEventsInteractor,
+) {
+
+    /**
+     * This is the ratio between the pointer distance and the dialog offset. The pointer has to
+     * travel this distance for a single point of an offset.
+     *
+     * When greater than 1 this makes the dialog to follow the touch behind.
+     */
+    private val offsetToTranslationRatio: Float = 3f
+    private val maxDeviation: Float =
+        context.resources
+            .getDimensionPixelSize(R.dimen.volume_dialog_slider_max_deviation)
+            .toFloat()
+    private val offsetInterpolator = PathInterpolator(0.15f, 0.00f, 0.20f, 1.00f)
+
+    private val sliderValue = MutableStateFlow<Slider?>(null)
+
+    val overscrollEvent: Flow<OverscrollEventModel> =
+        sliderValue
+            .filterNotNull()
+            .map { slider ->
+                when (slider.value) {
+                    slider.min -> 1f
+                    slider.max -> -1f
+                    else -> 0f
+                }
+            }
+            .distinctUntilChanged()
+            .flatMapLatest { direction ->
+                if (direction == 0f) {
+                    flowOf(OverscrollEventModel.Animate(0f))
+                } else {
+                    overscrollEvents(direction)
+                }
+            }
+
+    fun setSlider(value: Float, min: Float, max: Float) {
+        sliderValue.value = Slider(value = value, min = min, max = max)
+    }
+
+    /**
+     * Returns a flow that for each another [MotionEvent] it receives maps into a path from the
+     * first event.
+     *
+     * Emits [OverscrollEventModel.Move] that follows the [SliderInputEvent.Touch] from the pointer
+     * down position. Emits [OverscrollEventModel.Animate] when the gesture is terminated to create
+     * a spring-back effect.
+     */
+    private fun overscrollEvents(direction: Float): Flow<OverscrollEventModel> {
+        var startPosition: Float? = null
+        return inputEventsInteractor.event
+            .mapNotNull { (it as? SliderInputEvent.Touch)?.event }
+            .transform { touchEvent ->
+                // Skip events from inside the slider bounds for the case when the user adjusts
+                // slider
+                // towards max when the slider is already on max value.
+                if (touchEvent.isFinalEvent()) {
+                    startPosition = null
+                    emit(OverscrollEventModel.Animate(0f))
+                    return@transform
+                }
+                val currentStartPosition = startPosition
+                val newPosition: Float = touchEvent.rawY
+                if (currentStartPosition == null) {
+                    startPosition = newPosition
+                } else {
+                    val offset = (newPosition - currentStartPosition) / offsetToTranslationRatio
+                    val interpolatedOffset =
+                        if (areOfTheSameSign(direction, offset)) {
+                            sign(offset) *
+                                (maxDeviation *
+                                    offsetInterpolator.getInterpolation(
+                                        (abs(offset)) / maxDeviation
+                                    ))
+                        } else {
+                            0f
+                        }
+                    emit(OverscrollEventModel.Move(interpolatedOffset))
+                }
+            }
+    }
+
+    /** @return true when the [MotionEvent] indicates the end of the gesture. */
+    private fun MotionEvent.isFinalEvent(): Boolean {
+        return actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL
+    }
+
+    /** Models overscroll event */
+    sealed interface OverscrollEventModel {
+
+        /** Notifies the consumed to move by the [touchOffsetPx]. */
+        data class Move(val touchOffsetPx: Float) : OverscrollEventModel
+
+        /** Notifies the consume to animate to the [targetOffsetPx]. */
+        data class Animate(val targetOffsetPx: Float) : OverscrollEventModel
+    }
+
+    private data class Slider(val value: Float, val min: Float, val max: Float)
+}
+
+private fun areOfTheSameSign(lhs: Float, rhs: Float): Boolean =
+    when {
+        lhs < 0 -> rhs < 0
+        lhs > 0 -> rhs > 0
+        else -> rhs == 0f
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 67e03e4..82bf5e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.SystemUIAppComponentFactoryBase
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.dock.DockManagerFake
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -133,6 +134,7 @@
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = userTracker,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
         val remoteUserSelectionManager =
@@ -203,6 +205,7 @@
                 biometricSettingsRepository = biometricSettingsRepository,
                 backgroundDispatcher = testDispatcher,
                 appContext = mContext,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 sceneInteractor = { kosmos.sceneInteractor },
             )
         underTest.previewManager =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 21dd5bc..111d819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -20,7 +20,6 @@
 import android.app.admin.DevicePolicyManager
 import android.content.Intent
 import android.os.UserHandle
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
@@ -30,6 +29,7 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.dock.DockManagerFake
 import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.flags.FakeFeatureFlags
@@ -64,6 +64,7 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
@@ -82,7 +83,7 @@
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 @DisableSceneContainer
-@FlakyTest(bugId = 292574995, detail = "NullPointer on MockMakerTypeMockability.mockable()")
+@Ignore("b/292574995 NullPointer on MockMakerTypeMockability.mockable()")
 class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
 
     companion object {
@@ -270,6 +271,7 @@
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = userTracker,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
         val remoteUserSelectionManager =
@@ -320,6 +322,7 @@
                 biometricSettingsRepository = biometricSettingsRepository,
                 backgroundDispatcher = testDispatcher,
                 appContext = mContext,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 sceneInteractor = { kosmos.sceneInteractor },
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index 1ce128c..8c00047 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.dock.DockManagerFake
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.FakeFeatureFlags
@@ -273,6 +274,7 @@
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = userTracker,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
         val remoteUserSelectionManager =
@@ -323,6 +325,7 @@
                 biometricSettingsRepository = biometricSettingsRepository,
                 backgroundDispatcher = testDispatcher,
                 appContext = mContext,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 sceneInteractor = { kosmos.sceneInteractor },
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 3364528..84976a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dock.DockManagerFake
 import com.android.systemui.flags.FakeFeatureFlags
@@ -216,6 +217,7 @@
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = userTracker,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
         val remoteUserSelectionManager =
@@ -295,6 +297,7 @@
                         biometricSettingsRepository = biometricSettingsRepository,
                         backgroundDispatcher = kosmos.testDispatcher,
                         appContext = mContext,
+                        communalSettingsInteractor = kosmos.communalSettingsInteractor,
                         sceneInteractor = { kosmos.sceneInteractor },
                     ),
                 keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
index 503fa78..1eb88c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
@@ -89,6 +89,7 @@
                 messagingStyle = null,
                 builder = notificationBuilder,
                 systemUiContext = context,
+                redactText = false,
             )
 
         // WHEN: binds the viewHolder
@@ -149,6 +150,7 @@
                 messagingStyle = style,
                 builder = notificationBuilder,
                 systemUiContext = context,
+                redactText = false,
             )
         // WHEN: binds the view
         SingleLineViewBinder.bind(viewModel, view)
@@ -197,6 +199,7 @@
                 messagingStyle = null,
                 builder = notificationBuilder,
                 systemUiContext = context,
+                redactText = false,
             )
         // WHEN: binds the view with the view model
         SingleLineViewBinder.bind(viewModel, view)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
index d366632..ef70e27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
@@ -379,7 +379,8 @@
             this,
             if (isConversation) messagingStyle else null,
             builder,
-            context
+            context,
+            false
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index eb1e28b..9d6eb79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -65,7 +65,6 @@
     private float mPanelExpansion;
     private int mKeyguardStatusBarHeaderHeight;
     private int mKeyguardStatusHeight;
-    private int mUserSwitchHeight;
     private float mDark;
     private float mQsExpansion;
     private int mCutoutTopInset = 0;
@@ -317,30 +316,6 @@
     }
 
     @Test
-    public void notifPaddingAccountsForMultiUserSwitcherInSplitShade() {
-        setSplitShadeTopMargin(100);
-        mUserSwitchHeight = 150;
-        givenLockScreen();
-        mIsSplitShade = true;
-        // WHEN the position algorithm is run
-        positionClock();
-        // THEN the notif padding is split shade top margin + user switch height
-        assertThat(mClockPosition.stackScrollerPadding).isEqualTo(250);
-    }
-
-    @Test
-    public void clockDoesntAccountForMultiUserSwitcherInSplitShade() {
-        setSplitShadeTopMargin(100);
-        mUserSwitchHeight = 150;
-        givenLockScreen();
-        mIsSplitShade = true;
-        // WHEN the position algorithm is run
-        positionClock();
-        // THEN clockY = split shade top margin
-        assertThat(mClockPosition.clockY).isEqualTo(100);
-    }
-
-    @Test
     public void notifPaddingExpandedAlignedWithClockInSplitShadeMode() {
         givenLockScreen();
         mIsSplitShade = true;
@@ -370,9 +345,7 @@
         mIsSplitShade = false;
         mBypassEnabled = false;
 
-        // mMinTopMargin = 100 = 80 + max(20, 0)
-        mKeyguardStatusBarHeaderHeight = 80;
-        mUserSwitchHeight = 20;
+        mKeyguardStatusBarHeaderHeight = 100;
         when(mResources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin))
                 .thenReturn(0);
 
@@ -636,8 +609,6 @@
                 mKeyguardStatusBarHeaderHeight,
                 mPanelExpansion,
                 mKeyguardStatusHeight,
-                mUserSwitchHeight,
-                0 /* userSwitchPreferredY */,
                 mDark,
                 ZERO_DRAG,
                 mBypassEnabled,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index bfc4248..89aad4b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -99,10 +99,6 @@
 }
 
 suspend fun Kosmos.setCommunalV2Available(available: Boolean) {
-    setCommunalV2ConfigEnabled(true)
-    setCommunalEnabled(available)
-    with(fakeKeyguardRepository) {
-        setIsEncryptedOrLockdown(!available)
-        setKeyguardShowing(available)
-    }
+    setCommunalV2ConfigEnabled(available)
+    setCommunalAvailable(available)
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigKosmos.kt
new file mode 100644
index 0000000..5683248
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.applicationContext
+import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+
+val Kosmos.glanceableHubQuickAffordanceConfig by
+    Kosmos.Fixture {
+        GlanceableHubQuickAffordanceConfig(
+            context = applicationContext,
+            communalInteractor = communalInteractor,
+            communalSceneRepository = communalSceneRepository,
+            communalSettingsInteractor = communalSettingsInteractor,
+            sceneInteractor = sceneInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/LocalUserSelectionManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/LocalUserSelectionManagerKosmos.kt
index 21d1a76..328338b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/LocalUserSelectionManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/LocalUserSelectionManagerKosmos.kt
@@ -18,6 +18,7 @@
 
 import android.content.applicationContext
 import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.settings.userFileManager
 import com.android.systemui.settings.userTracker
@@ -28,6 +29,7 @@
             context = applicationContext,
             userFileManager = userFileManager,
             userTracker = userTracker,
+            communalSettingsInteractor = communalSettingsInteractor,
             broadcastDispatcher = broadcastDispatcher,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
index 009d17e..3b1199a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.internal.widget.lockPatternUtils
 import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
 import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.dock.dockManager
 import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
@@ -52,6 +53,7 @@
         devicePolicyManager = devicePolicyManager,
         dockManager = dockManager,
         biometricSettingsRepository = biometricSettingsRepository,
+        communalSettingsInteractor = communalSettingsInteractor,
         backgroundDispatcher = testDispatcher,
         appContext = applicationContext,
         sceneInteractor = { sceneInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt
index 0ec8d49..49bbbef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt
@@ -17,6 +17,6 @@
 package com.android.systemui.plugins
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
+import org.mockito.kotlin.mock
 
 var Kosmos.activityStarter by Kosmos.Fixture { mock<ActivityStarter>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
index ab1c181..aa29808 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
@@ -45,7 +45,6 @@
             other ?: return
         }
         check("icon").that(actual.icon).isEqualTo(other.icon)
-        check("iconRes").that(actual.iconRes).isEqualTo(other.iconRes)
         check("label").that(actual.label).isEqualTo(other.label)
         check("activationState").that(actual.activationState).isEqualTo(other.activationState)
         check("secondaryLabel").that(actual.secondaryLabel).isEqualTo(other.secondaryLabel)
diff --git a/core/tests/coretests/src/android/graphics/GraphicsTests.java b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/NotificationShadeWindowViewKosmos.kt
similarity index 62%
copy from core/tests/coretests/src/android/graphics/GraphicsTests.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/NotificationShadeWindowViewKosmos.kt
index 70f5976..18c44ba 100644
--- a/core/tests/coretests/src/android/graphics/GraphicsTests.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/NotificationShadeWindowViewKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,15 +14,9 @@
  * limitations under the License.
  */
 
-package android.graphics;
+package com.android.systemui.shade
 
-import junit.framework.TestSuite;
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
 
-public class GraphicsTests {
-    public static TestSuite suite() {
-        TestSuite suite = new TestSuite(GraphicsTests.class.getName());
-
-        suite.addTestSuite(BitmapTest.class);
-        return suite;
-    }
-}
+var Kosmos.notificationShadeWindowView by Kosmos.Fixture { mock<NotificationShadeWindowView>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt
index e5a75d5..9f4091c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt
@@ -18,8 +18,14 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import org.mockito.kotlin.mock
+
+var Kosmos.lockscreenShadeKeyguardTransitionController by Fixture {
+    mock<LockscreenShadeKeyguardTransitionController>()
+}
 
 var Kosmos.lockscreenShadeKeyguardTransitionControllerFactory by Fixture {
-    mock<LockscreenShadeKeyguardTransitionController.Factory>()
+    LockscreenShadeKeyguardTransitionController.Factory {
+        lockscreenShadeKeyguardTransitionController
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt
index 2767980..fc52e45 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt
@@ -18,8 +18,12 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import org.mockito.kotlin.mock
+
+var Kosmos.lockscreenShadeQsTransitionController by Fixture {
+    mock<LockscreenShadeQsTransitionController>()
+}
 
 var Kosmos.lockscreenShadeQsTransitionControllerFactory by Fixture {
-    mock<LockscreenShadeQsTransitionController.Factory>()
+    LockscreenShadeQsTransitionController.Factory { lockscreenShadeQsTransitionController }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
index e4a3896..1a451ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
@@ -36,7 +36,7 @@
 import com.android.systemui.statusbar.policy.configurationController
 import com.android.systemui.statusbar.policy.splitShadeStateController
 
-val Kosmos.lockscreenShadeTransitionController by Fixture {
+var Kosmos.lockscreenShadeTransitionController by Fixture {
     LockscreenShadeTransitionController(
         statusBarStateController = sysuiStatusBarStateController,
         logger = lsShadeTransitionLogger,
@@ -62,6 +62,6 @@
         splitShadeStateController = splitShadeStateController,
         shadeLockscreenInteractorLazy = { shadeLockscreenInteractor },
         naturalScrollingSettingObserver = naturalScrollingSettingObserver,
-        lazyQSSceneAdapter = { qsSceneAdapter }
+        lazyQSSceneAdapter = { qsSceneAdapter },
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt
index 43e39c0..8a68eef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import org.mockito.kotlin.mock
 
 var Kosmos.singleShadeLockScreenOverScrollerFactory by Fixture {
     mock<SingleShadeLockScreenOverScroller.Factory>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt
index 017371a..e491dff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt
@@ -18,8 +18,10 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import org.mockito.kotlin.mock
+
+var Kosmos.splitShadeLockScreenOverScroller by Fixture { mock<SplitShadeLockScreenOverScroller>() }
 
 var Kosmos.splitShadeLockScreenOverScrollerFactory by Fixture {
-    mock<SplitShadeLockScreenOverScroller.Factory>()
+    SplitShadeLockScreenOverScroller.Factory { _, _ -> splitShadeLockScreenOverScroller }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisualInterruptionDecisionProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisualInterruptionDecisionProviderKosmos.kt
new file mode 100644
index 0000000..360e9e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisualInterruptionDecisionProviderKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
+import org.mockito.kotlin.mock
+
+val Kosmos.visualInterruptionDecisionProvider by
+    Kosmos.Fixture { mock<VisualInterruptionDecisionProvider>() }
diff --git a/core/tests/coretests/src/android/graphics/GraphicsTests.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorKosmos.kt
similarity index 62%
rename from core/tests/coretests/src/android/graphics/GraphicsTests.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorKosmos.kt
index 70f5976..768952d 100644
--- a/core/tests/coretests/src/android/graphics/GraphicsTests.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,15 +14,9 @@
  * limitations under the License.
  */
 
-package android.graphics;
+package com.android.systemui.statusbar.notification.domain.interactor
 
-import junit.framework.TestSuite;
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
 
-public class GraphicsTests {
-    public static TestSuite suite() {
-        TestSuite suite = new TestSuite(GraphicsTests.class.getName());
-
-        suite.addTestSuite(BitmapTest.class);
-        return suite;
-    }
-}
+val Kosmos.notificationAlertsInteractor by Kosmos.Fixture { mock<NotificationAlertsInteractor>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
index f19ac1e..26642d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.policy
 
 import com.android.systemui.kosmos.Kosmos
-import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
 
 var Kosmos.keyguardStateController: KeyguardStateController by
-    Kosmos.Fixture { mock(KeyguardStateController::class.java) }
+    Kosmos.Fixture { mock<KeyguardStateController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
index 34661ce..4fda95b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyInteractor
+import com.android.systemui.statusbar.policy.configurationController
 import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.ringer.domain.volumeDialogRingerInteractor
 import com.android.systemui.volume.dialog.shared.volumeDialogLogger
@@ -37,5 +38,6 @@
             vibrator = vibratorHelper,
             volumeDialogLogger = volumeDialogLogger,
             visibilityInteractor = volumeDialogVisibilityInteractor,
+            configurationController = configurationController,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
index 423100a..44917dd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.dialog.sliders.domain.interactor
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.plugins.volumeDialogController
 import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
 import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
@@ -25,6 +26,7 @@
     Kosmos.Fixture {
         VolumeDialogSliderInteractor(
             volumeDialogSliderType,
+            applicationCoroutineScope,
             volumeDialogStateInteractor,
             volumeDialogController,
         )
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c15cf34..6858e29 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -31,6 +31,7 @@
 per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
 per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
 per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
+per-file GestureLauncherService.java = file:/INPUT_OWNERS
 per-file MmsServiceBroker.java = file:/telephony/OWNERS
 per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
 per-file PackageWatchdog.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 719928b..09440e0 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5266,6 +5266,22 @@
             }
         }
 
+        @Override
+        public void onNullBinding(ComponentName name) {
+            IAccountManagerResponse response = getResponseAndClose();
+            if (response != null) {
+                try {
+                    response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+                            "disconnected");
+                } catch (RemoteException e) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Session.onNullBinding: "
+                                + "caught RemoteException while responding", e);
+                    }
+                }
+            }
+        }
+
         public abstract void run() throws RemoteException;
 
         public void onTimedOut() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 4944caf..4b8770b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
@@ -31,6 +32,7 @@
 import static com.android.server.am.BroadcastConstants.getDeviceConfigBoolean;
 
 import android.annotation.NonNull;
+import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
 import android.app.ForegroundServiceTypePolicy;
 import android.content.ComponentName;
@@ -55,6 +57,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -181,6 +184,12 @@
     static final String KEY_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION =
             "follow_up_oomadj_update_wait_duration";
 
+    /*
+     * Oom score cutoff beyond which any process that does not have the CPU_TIME capability will be
+     * frozen.
+     */
+    static final String KEY_FREEZER_CUTOFF_ADJ = "freezer_cutoff_adj";
+
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024;
     private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true;
     private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
@@ -267,6 +276,9 @@
      */
     private static final long DEFAULT_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION = 1000L;
 
+    /** The default value to {@link #KEY_FREEZER_CUTOFF_ADJ} */
+    private static final int DEFAULT_FREEZER_CUTOFF_ADJ = ProcessList.CACHED_APP_MIN_ADJ;
+
     /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
      */
@@ -1171,6 +1183,14 @@
             DEFAULT_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION;
 
     /**
+     * The cutoff adj for the freezer, app processes with adj greater than this value will be
+     * eligible for the freezer.
+     *
+     * @see #KEY_FREEZER_CUTOFF_ADJ
+     */
+    public int FREEZER_CUTOFF_ADJ = DEFAULT_FREEZER_CUTOFF_ADJ;
+
+    /**
      * Indicates whether PSS profiling in AppProfiler is disabled or not.
      */
     static final String KEY_DISABLE_APP_PROFILER_PSS_PROFILING =
@@ -1194,6 +1214,7 @@
             new OnPropertiesChangedListener() {
                 @Override
                 public void onPropertiesChanged(Properties properties) {
+                    boolean oomAdjusterConfigUpdated = false;
                     for (String name : properties.getKeyset()) {
                         if (name == null) {
                             return;
@@ -1372,6 +1393,11 @@
                             case KEY_TIERED_CACHED_ADJ_UI_TIER_SIZE:
                                 updateUseTieredCachedAdj();
                                 break;
+                            case KEY_FREEZER_CUTOFF_ADJ:
+                                FREEZER_CUTOFF_ADJ = properties.getInt(KEY_FREEZER_CUTOFF_ADJ,
+                                        DEFAULT_FREEZER_CUTOFF_ADJ);
+                                oomAdjusterConfigUpdated = true;
+                                break;
                             case KEY_DISABLE_APP_PROFILER_PSS_PROFILING:
                                 updateDisableAppProfilerPssProfiling();
                                 break;
@@ -1389,6 +1415,13 @@
                                 break;
                         }
                     }
+                    if (oomAdjusterConfigUpdated) {
+                        final ActivityManagerInternal ami = LocalServices.getService(
+                                ActivityManagerInternal.class);
+                        if (ami != null) {
+                            ami.updateOomAdj(OOM_ADJ_REASON_RECONFIGURATION);
+                        }
+                    }
                 }
             };
 
@@ -2534,6 +2567,9 @@
         pw.print("  "); pw.print(KEY_ENABLE_NEW_OOMADJ);
         pw.print("="); pw.println(ENABLE_NEW_OOMADJ);
 
+        pw.print("  "); pw.print(KEY_FREEZER_CUTOFF_ADJ);
+        pw.print("="); pw.println(FREEZER_CUTOFF_ADJ);
+
         pw.print("  "); pw.print(KEY_DISABLE_APP_PROFILER_PSS_PROFILING);
         pw.print("="); pw.println(APP_PROFILER_PSS_PROFILING_DISABLED);
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 9a63546..cbebc90 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -449,6 +449,8 @@
                     return runSetAppZygotePreloadTimeout(pw);
                 case "set-media-foreground-service":
                     return runSetMediaForegroundService(pw);
+                case "clear-bad-process":
+                    return runClearBadProcess(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -4276,6 +4278,27 @@
         return 0;
     }
 
+    int runClearBadProcess(PrintWriter pw) throws RemoteException {
+        final String processName = getNextArgRequired();
+        int userId = UserHandle.USER_CURRENT;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if ("--user".equals(opt)) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: unknown option " + opt);
+                return -1;
+            }
+        }
+        if (userId == UserHandle.USER_CURRENT) {
+            userId = mInternal.getCurrentUserId();
+        }
+
+        pw.println("Clearing '" + processName + "' in u" + userId + " from bad processes list");
+        mInternal.mAppErrors.clearBadProcessForUser(processName, userId);
+        return 0;
+    }
+
     private Resources getResources(PrintWriter pw) throws RemoteException {
         // system resources does not contain all the device configuration, construct it manually.
         Configuration config = mInterface.getConfiguration();
@@ -4717,6 +4740,8 @@
             pw.println("  set-media-foreground-service inactive|active [--user USER_ID] <PACKAGE>"
                             + " <NOTIFICATION_ID>");
             pw.println("         Set an app's media service inactive or active.");
+            pw.println("  clear-bad-process [--user USER_ID] <PROCESS_NAME>");
+            pw.println("         Clears a process from the bad processes list.");
             Intent.printIntentArgsHelp(pw, "");
         }
     }
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index b7a5f3e..2fbf05e 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -373,6 +373,24 @@
         }
     }
 
+    void clearBadProcessForUser(final String processName, final int userId) {
+        synchronized (mBadProcessLock) {
+            final ProcessMap<BadProcessInfo> badProcesses = new ProcessMap<>();
+            badProcesses.putAll(mBadProcesses);
+            final SparseArray<BadProcessInfo> uids = badProcesses.get(processName);
+            if (uids == null) {
+                return;
+            }
+            for (int i = uids.size() - 1; i >= 0; --i) {
+                final int uid = uids.keyAt(i);
+                if (userId == UserHandle.USER_ALL || userId == UserHandle.getUserId(uid)) {
+                    badProcesses.remove(processName, uid);
+                }
+            }
+            mBadProcesses = badProcesses;
+        }
+    }
+
     void markBadProcess(final String processName, final int uid, BadProcessInfo info) {
         synchronized (mBadProcessLock) {
             final ProcessMap<BadProcessInfo> badProcesses = new ProcessMap<>();
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 2f5362f..d335529 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -25,9 +25,11 @@
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FOLLOW_UP;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
@@ -203,6 +205,10 @@
             FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE;
     static final int UNFREEZE_REASON_COMPONENT_DISABLED =
             FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED;
+    static final int UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_OOM_ADJ_FOLLOW_UP;
+    static final int UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION =
+            FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_OOM_ADJ_RECONFIGURATION;
 
     @IntDef(prefix = {"UNFREEZE_REASON_"}, value = {
         UNFREEZE_REASON_NONE,
@@ -234,6 +240,8 @@
         UNFREEZE_REASON_EXECUTING_SERVICE,
         UNFREEZE_REASON_RESTRICTION_CHANGE,
         UNFREEZE_REASON_COMPONENT_DISABLED,
+        UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP,
+        UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UnfreezeReason {}
@@ -2451,8 +2459,8 @@
                             synchronized (mAm.mPidsSelfLocked) {
                                 pr = mAm.mPidsSelfLocked.get(blocked);
                             }
-                            if (pr != null
-                                    && pr.mState.getCurAdj() < ProcessList.FREEZER_CUTOFF_ADJ) {
+                            if (pr != null && pr.mState.getCurAdj()
+                                    < mAm.mConstants.FREEZER_CUTOFF_ADJ) {
                                 Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
                                         + pr.processName + " (" + blocked + ")");
                                 // Found at least one blocked non-cached process
@@ -2539,6 +2547,10 @@
                 return UNFREEZE_REASON_RESTRICTION_CHANGE;
             case OOM_ADJ_REASON_COMPONENT_DISABLED:
                 return UNFREEZE_REASON_COMPONENT_DISABLED;
+            case OOM_ADJ_REASON_FOLLOW_UP:
+                return UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP;
+            case OOM_ADJ_REASON_RECONFIGURATION:
+                return UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION;
             default:
                 return UNFREEZE_REASON_NONE;
         }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index aadf6f6..9c569db 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -55,6 +55,7 @@
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
@@ -105,7 +106,6 @@
 import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
 import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ;
-import static com.android.server.am.ProcessList.FREEZER_CUTOFF_ADJ;
 import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ;
 import static com.android.server.am.ProcessList.HOME_APP_ADJ;
 import static com.android.server.am.ProcessList.INVALID_ADJ;
@@ -232,6 +232,8 @@
                 return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED;
             case OOM_ADJ_REASON_FOLLOW_UP:
                 return AppProtoEnums.OOM_ADJ_REASON_FOLLOW_UP;
+            case OOM_ADJ_REASON_RECONFIGURATION:
+                return AppProtoEnums.OOM_ADJ_REASON_RECONFIGURATION;
             default:
                 return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
         }
@@ -288,6 +290,8 @@
                 return OOM_ADJ_REASON_METHOD + "_componentDisabled";
             case OOM_ADJ_REASON_FOLLOW_UP:
                 return OOM_ADJ_REASON_METHOD + "_followUp";
+            case OOM_ADJ_REASON_RECONFIGURATION:
+                return OOM_ADJ_REASON_METHOD + "_reconfiguration";
             default:
                 return "_unknown";
         }
@@ -4079,7 +4083,7 @@
         }
 
         // Reasons to freeze:
-        if (proc.mState.getCurAdj() >= FREEZER_CUTOFF_ADJ) {
+        if (proc.mState.getCurAdj() >= mConstants.FREEZER_CUTOFF_ADJ) {
             // Oomscore is in a high enough state, it is safe to freeze.
             return true;
         }
@@ -4098,9 +4102,8 @@
         final ProcessCachedOptimizerRecord opt = app.mOptRecord;
         final ProcessStateRecord state = app.mState;
         if (Flags.traceUpdateAppFreezeStateLsp()) {
-            final boolean oomAdjChanged =
-                    (state.getCurAdj() >= FREEZER_CUTOFF_ADJ ^ oldOomAdj >= FREEZER_CUTOFF_ADJ)
-                    || oldOomAdj == UNKNOWN_ADJ;
+            final boolean oomAdjChanged = (state.getCurAdj() >= mConstants.FREEZER_CUTOFF_ADJ
+                    ^ oldOomAdj >= mConstants.FREEZER_CUTOFF_ADJ) || oldOomAdj == UNKNOWN_ADJ;
             final boolean shouldNotFreezeChanged = opt.shouldNotFreezeAdjSeq() == mAdjSeq;
             final boolean hasCpuCapability =
                     (PROCESS_CAPABILITY_CPU_TIME & app.mState.getCurCapability())
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 70febcd..bddde9d 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -383,12 +383,6 @@
     private static final long LMKD_RECONNECT_DELAY_MS = 1000;
 
     /**
-     * The cuttoff adj for the freezer, app processes with adj greater than this value will be
-     * eligible for the freezer.
-     */
-    static final int FREEZER_CUTOFF_ADJ = CACHED_APP_MIN_ADJ;
-
-    /**
      * Apps have no access to the private data directories of any other app, even if the other
      * app has made them world-readable.
      */
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 98f738c..49149e1 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1699,7 +1699,7 @@
         return mService.mOomAdjuster.mCachedAppOptimizer.useFreezer()
                 && !mOptRecord.isFreezeExempt()
                 && !mOptRecord.shouldNotFreeze()
-                && mState.getCurAdj() >= ProcessList.FREEZER_CUTOFF_ADJ;
+                && mState.getCurAdj() >= mService.mConstants.FREEZER_CUTOFF_ADJ;
     }
 
     public void forEachConnectionHost(Consumer<ProcessRecord> consumer) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 06c586f..295e044 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3191,7 +3191,7 @@
                     resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
                     Process.INVALID_UID, null, null,
                     Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted,
-                    "proxy " + message, shouldCollectMessage);
+                    "proxy " + message, shouldCollectMessage, 1);
             if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
                 return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
                         proxiedPackageName);
@@ -3210,7 +3210,20 @@
         return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
                 proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName,
                 proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp,
-                message, shouldCollectMessage);
+                message, shouldCollectMessage, 1);
+    }
+
+    @Override
+    public void noteOperationsInBatch(Map batchedNoteOps) {
+        for (var entry : ((Map<AppOpsManager.NotedOp, Integer>) batchedNoteOps).entrySet()) {
+            AppOpsManager.NotedOp notedOp = entry.getKey();
+            int notedCount = entry.getValue();
+            mCheckOpsDelegateDispatcher.noteOperation(
+                    notedOp.getOp(), notedOp.getUid(), notedOp.getPackageName(),
+                    notedOp.getAttributionTag(), notedOp.getVirtualDeviceId(),
+                    notedOp.getShouldCollectAsyncNotedOp(), notedOp.getMessage(),
+                    notedOp.getShouldCollectMessage(), notedCount);
+        }
     }
 
     @Override
@@ -3228,7 +3241,7 @@
         }
         return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
                 attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage);
+                shouldCollectMessage, 1);
     }
 
     @Override
@@ -3237,13 +3250,12 @@
             String message, boolean shouldCollectMessage) {
         return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
                 attributionTag, virtualDeviceId, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage);
+                shouldCollectMessage, 1);
     }
 
     private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
-             @Nullable String attributionTag, int virtualDeviceId,
-             boolean shouldCollectAsyncNotedOp, @Nullable String message,
-             boolean shouldCollectMessage) {
+            @Nullable String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
+            @Nullable String message, boolean shouldCollectMessage, int notedCount) {
         String resolvedPackageName;
         if (!shouldUseNewCheckOp()) {
             verifyIncomingUid(uid);
@@ -3278,14 +3290,14 @@
         return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
                 virtualDeviceId, Process.INVALID_UID, null, null,
                 Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp,
-                message, shouldCollectMessage);
+                message, shouldCollectMessage, notedCount);
     }
 
     private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
             @Nullable String attributionTag, int virtualDeviceId, int proxyUid,
             String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId,
             @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message,
-            boolean shouldCollectMessage) {
+            boolean shouldCollectMessage, int notedCount) {
         PackageVerificationResult pvr;
         try {
             pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3388,11 +3400,11 @@
                     virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
 
             attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
-                    getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags);
+                    getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags, notedCount);
 
             if (shouldCollectAsyncNotedOp) {
                 collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
-                        shouldCollectMessage);
+                        shouldCollectMessage, notedCount);
             }
 
             return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
@@ -3551,7 +3563,7 @@
      */
     private void collectAsyncNotedOp(int uid, @NonNull String packageName, int opCode,
             @Nullable String attributionTag, @OpFlags int flags, @NonNull String message,
-            boolean shouldCollectMessage) {
+            boolean shouldCollectMessage, int notedCount) {
         Objects.requireNonNull(message);
 
         int callingUid = Binder.getCallingUid();
@@ -3559,42 +3571,51 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
-
-                RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
-                AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
-                        attributionTag, message, System.currentTimeMillis());
-                final boolean[] wasNoteForwarded = {false};
-
                 if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) != 0
                         && shouldCollectMessage) {
                     reportRuntimeAppOpAccessMessageAsyncLocked(uid, packageName, opCode,
                             attributionTag, message);
                 }
 
-                if (callbacks != null) {
+                Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+                RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+                if (callbacks == null) {
+                    return;
+                }
+
+                final boolean[] wasNoteForwarded = {false};
+                if (Flags.rateLimitBatchedNoteOpAsyncCallbacksEnabled()) {
+                    notedCount = 1;
+                }
+
+                for (int i = 0; i < notedCount; i++) {
+                    AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
+                            attributionTag, message, System.currentTimeMillis());
+                    wasNoteForwarded[0] = false;
                     callbacks.broadcast((cb) -> {
                         try {
                             cb.opNoted(asyncNotedOp);
                             wasNoteForwarded[0] = true;
                         } catch (RemoteException e) {
                             Slog.e(TAG,
-                                    "Could not forward noteOp of " + opCode + " to " + packageName
+                                    "Could not forward noteOp of " + opCode + " to "
+                                            + packageName
                                             + "/" + uid + "(" + attributionTag + ")", e);
                         }
                     });
-                }
 
-                if (!wasNoteForwarded[0]) {
-                    ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(key);
-                    if (unforwardedOps == null) {
-                        unforwardedOps = new ArrayList<>(1);
-                        mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
-                    }
+                    if (!wasNoteForwarded[0]) {
+                        ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(
+                                key);
+                        if (unforwardedOps == null) {
+                            unforwardedOps = new ArrayList<>(1);
+                            mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
+                        }
 
-                    unforwardedOps.add(asyncNotedOp);
-                    if (unforwardedOps.size() > MAX_UNFORWARDED_OPS) {
-                        unforwardedOps.remove(0);
+                        unforwardedOps.add(asyncNotedOp);
+                        if (unforwardedOps.size() > MAX_UNFORWARDED_OPS) {
+                            unforwardedOps.remove(0);
+                        }
                     }
                 }
             }
@@ -4026,7 +4047,7 @@
 
         if (shouldCollectAsyncNotedOp && !isRestricted) {
             collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
-                    message, shouldCollectMessage);
+                    message, shouldCollectMessage, 1);
         }
 
         return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
@@ -7574,34 +7595,36 @@
 
         public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
                 String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
-                String message, boolean shouldCollectMessage) {
+                String message, boolean shouldCollectMessage, int notedCount) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
                     return mPolicy.noteOperation(code, uid, packageName, attributionTag,
                             virtualDeviceId, shouldCollectAsyncNotedOp, message,
-                            shouldCollectMessage, this::noteDelegateOperationImpl
+                            shouldCollectMessage, notedCount, this::noteDelegateOperationImpl
                     );
                 } else {
                     return mPolicy.noteOperation(code, uid, packageName, attributionTag,
                             virtualDeviceId, shouldCollectAsyncNotedOp, message,
-                            shouldCollectMessage, AppOpsService.this::noteOperationImpl
+                            shouldCollectMessage, notedCount, AppOpsService.this::noteOperationImpl
                     );
                 }
             } else if (mCheckOpsDelegate != null) {
                 return noteDelegateOperationImpl(code, uid, packageName, attributionTag,
-                        virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                        virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                        notedCount);
             }
             return noteOperationImpl(code, uid, packageName, attributionTag,
-                    virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                    virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                    notedCount);
         }
 
         private SyncNotedAppOp noteDelegateOperationImpl(int code, int uid,
                 @Nullable String packageName, @Nullable String featureId, int virtualDeviceId,
                 boolean shouldCollectAsyncNotedOp, @Nullable String message,
-                boolean shouldCollectMessage) {
+                boolean shouldCollectMessage, int notedCount) {
             return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId,
                     virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                    AppOpsService.this::noteOperationImpl
+                    notedCount, AppOpsService.this::noteOperationImpl
             );
         }
 
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 314664b..4d114b4 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -100,10 +100,12 @@
      * @param proxyDeviceId       The device Id of the proxy
      * @param uidState            UID state of the app noteOp/startOp was called for
      * @param flags               OpFlags of the call
+     * @param accessCount         The number of times the op is accessed
      */
     public void accessed(int proxyUid, @Nullable String proxyPackageName,
             @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
-            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+            int accessCount) {
         long accessTime = System.currentTimeMillis();
         accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId,
                 uidState, flags);
@@ -111,7 +113,7 @@
         mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                 parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime,
                 AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE,
-                DiscreteRegistry.ACCESS_TYPE_NOTE_OP);
+                DiscreteRegistry.ACCESS_TYPE_NOTE_OP, accessCount);
     }
 
     /**
@@ -255,7 +257,7 @@
         if (isStarted) {
             mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                     parent.packageName, persistentDeviceId, tag, uidState, flags, startTime,
-                    attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP);
+                    attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP, 1);
         }
     }
 
@@ -451,7 +453,7 @@
             mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                     parent.packageName, persistentDeviceId, tag, event.getUidState(),
                     event.getFlags(), startTime, event.getAttributionFlags(),
-                    event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP);
+                    event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP, 1);
             if (shouldSendActive) {
                 mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
                         parent.packageName, tag, event.getVirtualDeviceId(), true,
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 6b02538..5e67f26 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -475,7 +475,7 @@
             @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
             @OpFlags int flags, long accessTime,
             @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
-            @DiscreteRegistry.AccessType int accessType) {
+            @DiscreteRegistry.AccessType int accessType, int accessCount) {
         synchronized (mInMemoryLock) {
             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                 if (!isPersistenceInitializedMLocked()) {
@@ -484,7 +484,7 @@
                 }
                 getUpdatedPendingHistoricalOpsMLocked(
                         System.currentTimeMillis()).increaseAccessCount(op, uid, packageName,
-                        attributionTag, uidState, flags, 1);
+                        attributionTag, uidState, flags, accessCount);
 
                 mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
                         attributionTag, flags, uidState, accessTime, -1, attributionFlags,
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index 4d5bce5..fedfe51 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -199,7 +199,9 @@
             for (AudioPlaybackConfiguration apc : players) {
                 final VolumeShaper.Configuration volShaper =
                         mFadeConfigurations.getFadeOutVolumeShaperConfig(apc.getAudioAttributes());
-                fa.addFade(apc, /* skipRamp= */ false, volShaper);
+                if (volShaper != null) {
+                    fa.addFade(apc, /* skipRamp= */ false, volShaper);
+                }
             }
         }
     }
@@ -249,7 +251,7 @@
             final VolumeShaper.Configuration volShaper =
                     mFadeConfigurations.getFadeOutVolumeShaperConfig(apc.getAudioAttributes());
             final FadedOutApp fa = mUidToFadedAppsMap.get(apc.getClientUid());
-            if (fa == null) {
+            if (fa == null || volShaper == null) {
                 return;
             }
             fa.addFade(apc, /* skipRamp= */ true, volShaper);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5740e16..f145a47 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -21,8 +21,11 @@
 import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
 import static android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT;
 import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
+import static android.Manifest.permission.CONFIGURE_WIFI_DISPLAY;
+import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.MANAGE_DISPLAYS;
+import static android.Manifest.permission.MODIFY_HDR_CONVERSION_MODE;
 import static android.Manifest.permission.RESTRICT_DISPLAY_MODES;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
@@ -119,6 +122,7 @@
 import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
+import android.os.IBinder.FrozenStateChangeCallback;
 import android.os.IThermalService;
 import android.os.Looper;
 import android.os.Message;
@@ -272,6 +276,7 @@
     private static final int MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE = 7;
     private static final int MSG_DELIVER_DISPLAY_GROUP_EVENT = 8;
     private static final int MSG_RECEIVED_DEVICE_STATE = 9;
+    private static final int MSG_DISPATCH_PENDING_PROCESS_EVENTS = 10;
     private static final int[] EMPTY_ARRAY = new int[0];
     private static final HdrConversionMode HDR_CONVERSION_MODE_UNSUPPORTED = new HdrConversionMode(
             HDR_CONVERSION_UNSUPPORTED);
@@ -286,7 +291,6 @@
     private InputManagerInternal mInputManagerInternal;
     private ActivityManagerInternal mActivityManagerInternal;
     private final UidImportanceListener mUidImportanceListener = new UidImportanceListener();
-    private final DisplayFrozenProcessListener mDisplayFrozenProcessListener;
 
     @Nullable
     private IMediaProjectionManager mProjectionService;
@@ -630,7 +634,6 @@
         mFlags = injector.getFlags();
         mHandler = new DisplayManagerHandler(displayThreadLooper);
         mHandlerExecutor = new HandlerExecutor(mHandler);
-        mDisplayFrozenProcessListener = new DisplayFrozenProcessListener();
         mUiHandler = UiThread.getHandler();
         mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
         mLogicalDisplayMapper = new LogicalDisplayMapper(mContext,
@@ -1165,31 +1168,11 @@
         }
     }
 
-    private class DisplayFrozenProcessListener
-            implements ActivityManagerInternal.FrozenProcessListener {
-        public void onProcessFrozen(int pid) {
-            synchronized (mSyncRoot) {
-                CallbackRecord callback = mCallbacks.get(pid);
-                if (callback == null) {
-                    return;
-                }
-                callback.setFrozen(true);
-            }
-        }
-
-        public void onProcessUnfrozen(int pid) {
-            // First, see if there is a callback associated with this pid.  If there's no
-            // callback, then there is nothing to do.
-            CallbackRecord callback;
-            synchronized (mSyncRoot) {
-                callback = mCallbacks.get(pid);
-                if (callback == null) {
-                    return;
-                }
-                callback.setFrozen(false);
-            }
-            // Attempt to dispatch pending events if the process is coming out of frozen.
+    private void dispatchPendingProcessEvents(@NonNull Object cb) {
+        if (cb instanceof CallbackRecord callback) {
             callback.dispatchPending();
+        } else {
+            Slog.wtf(TAG, "not a callback: " + cb);
         }
     }
 
@@ -4052,6 +4035,9 @@
                     deliverDisplayGroupEvent(msg.arg1, msg.arg2);
                     break;
 
+                case MSG_DISPATCH_PENDING_PROCESS_EVENTS:
+                    dispatchPendingProcessEvents(msg.obj);
+                    break;
             }
         }
     }
@@ -4119,7 +4105,7 @@
         }
     }
 
-    private final class CallbackRecord implements DeathRecipient {
+    private final class CallbackRecord implements DeathRecipient, FrozenStateChangeCallback {
         public final int mPid;
         public final int mUid;
         private final IDisplayManagerCallback mCallback;
@@ -4142,6 +4128,8 @@
         private boolean mCached;
         @GuardedBy("mCallback")
         private boolean mFrozen;
+        @GuardedBy("mCallback")
+        private boolean mAlive;
 
         CallbackRecord(int pid, int uid, @NonNull IDisplayManagerCallback callback,
                 @InternalEventFlag long internalEventFlagsMask) {
@@ -4151,18 +4139,20 @@
             mInternalEventFlagsMask = new AtomicLong(internalEventFlagsMask);
             mCached = false;
             mFrozen = false;
+            mAlive = true;
 
             if (deferDisplayEventsWhenFrozen()) {
-                // Some CallbackRecords are registered very early in system boot, before
-                // mActivityManagerInternal is initialized. If mActivityManagerInternal is null,
-                // do not register the frozen process listener.  However, do verify that all such
-                // registrations are for the self pid (which can never be frozen, so the frozen
-                // process listener does not matter).
-                if (mActivityManagerInternal != null) {
-                    mActivityManagerInternal.addFrozenProcessListener(pid, mHandlerExecutor,
-                            mDisplayFrozenProcessListener);
-                } else if (Process.myPid() != pid) {
-                    Slog.e(TAG, "DisplayListener registered too early");
+                try {
+                    callback.asBinder().addFrozenStateChangeCallback(this);
+                } catch (UnsupportedOperationException e) {
+                    // Ignore the exception.  The callback is not supported on this platform or on
+                    // this binder.  The callback is never supported for local binders.  There is
+                    // no error: the UID importance listener will still operate.  A log message is
+                    // provided for debug.
+                    Slog.v(TAG, "FrozenStateChangeCallback not supported for pid " + mPid);
+                } catch (RemoteException e) {
+                    // This is unexpected.  Just give up.
+                    throw new RuntimeException(e);
                 }
             }
 
@@ -4187,7 +4177,7 @@
          */
         @GuardedBy("mCallback")
         private boolean hasPendingAndIsReadyLocked() {
-            return isReadyLocked() && mPendingEvents != null && !mPendingEvents.isEmpty();
+            return isReadyLocked() && mPendingEvents != null && !mPendingEvents.isEmpty() && mAlive;
         }
 
         /**
@@ -4195,7 +4185,7 @@
          * receive events and there are pending events to be delivered.
          * This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
          */
-        public boolean setFrozen(boolean frozen) {
+        private boolean setFrozen(boolean frozen) {
             synchronized (mCallback) {
                 mFrozen = frozen;
                 return hasPendingAndIsReadyLocked();
@@ -4216,6 +4206,9 @@
 
         @Override
         public void binderDied() {
+            synchronized (mCallback) {
+                mAlive = false;
+            }
             if (DEBUG || extraLogging(mPackageName)) {
                 Slog.d(TAG, "Display listener for pid " + mPid + " died.");
             }
@@ -4226,6 +4219,14 @@
             onCallbackDied(this);
         }
 
+        @Override
+        public void onFrozenStateChanged(@NonNull IBinder who, int state) {
+            if (setFrozen(state == FrozenStateChangeCallback.STATE_FROZEN)) {
+                Message msg = mHandler.obtainMessage(MSG_DISPATCH_PENDING_PROCESS_EVENTS, this);
+                mHandler.sendMessage(msg);
+            }
+        }
+
         /**
          * @return {@code false} if RemoteException happens; otherwise {@code true} for
          * success.  This returns true even if the event was deferred because the remote client is
@@ -4392,7 +4393,7 @@
         // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
         public boolean dispatchPending() {
             synchronized (mCallback) {
-                if (mPendingEvents == null || mPendingEvents.isEmpty()) {
+                if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
                     return true;
                 }
                 if (!isReadyLocked()) {
@@ -4609,13 +4610,13 @@
             }
         }
 
+        @EnforcePermission(CONFIGURE_WIFI_DISPLAY)
         @Override // Binder call
         public void connectWifiDisplay(String address) {
+            connectWifiDisplay_enforcePermission();
             if (address == null) {
                 throw new IllegalArgumentException("address must not be null");
             }
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
-                    "Permission required to connect to a wifi display");
 
             final long token = Binder.clearCallingIdentity();
             try {
@@ -4640,13 +4641,13 @@
             }
         }
 
+        @EnforcePermission(CONFIGURE_WIFI_DISPLAY)
         @Override // Binder call
         public void renameWifiDisplay(String address, String alias) {
+            renameWifiDisplay_enforcePermission();
             if (address == null) {
                 throw new IllegalArgumentException("address must not be null");
             }
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
-                    "Permission required to rename to a wifi display");
 
             final long token = Binder.clearCallingIdentity();
             try {
@@ -4656,13 +4657,13 @@
             }
         }
 
+        @EnforcePermission(CONFIGURE_WIFI_DISPLAY)
         @Override // Binder call
         public void forgetWifiDisplay(String address) {
+            forgetWifiDisplay_enforcePermission();
             if (address == null) {
                 throw new IllegalArgumentException("address must not be null");
             }
-            mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
-                    "Permission required to forget to a wifi display");
 
             final long token = Binder.clearCallingIdentity();
             try {
@@ -5006,7 +5007,7 @@
             }
         }
 
-        @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(CONTROL_DISPLAY_BRIGHTNESS)
         @Override
         public BrightnessInfo getBrightnessInfo(int displayId) {
             getBrightnessInfo_enforcePermission();
@@ -5037,7 +5038,7 @@
             }
         }
 
-        @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(CONTROL_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setTemporaryBrightness(int displayId, float brightness) {
             setTemporaryBrightness_enforcePermission();
@@ -5052,7 +5053,7 @@
             }
         }
 
-        @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(CONTROL_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setBrightness(int displayId, float brightness) {
             setBrightness_enforcePermission();
@@ -5076,12 +5077,11 @@
             }
         }
 
+        @EnforcePermission(CONTROL_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public float getBrightness(int displayId) {
+            getBrightness_enforcePermission();
             float brightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
-                    "Permission required to set the display's brightness");
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mSyncRoot) {
@@ -5096,7 +5096,7 @@
             return brightness;
         }
 
-        @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(CONTROL_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
             setTemporaryAutoBrightnessAdjustment_enforcePermission();
@@ -5171,14 +5171,13 @@
             }
         }
 
+        @EnforcePermission(MODIFY_HDR_CONVERSION_MODE)
         @Override // Binder call
         public void setHdrConversionMode(HdrConversionMode hdrConversionMode) {
+            setHdrConversionMode_enforcePermission();
             if (!mIsHdrOutputControlEnabled) {
                 return;
             }
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.MODIFY_HDR_CONVERSION_MODE,
-                    "Permission required to set the HDR conversion mode.");
             final long token = Binder.clearCallingIdentity();
             try {
                 setHdrConversionModeInternal(hdrConversionMode);
@@ -5342,7 +5341,7 @@
                     ? ddc.getHdrBrightnessData().highestHdrSdrRatio : 1;
         }
 
-        @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(CONTROL_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
             getDozeBrightnessSensorValueToBrightness_enforcePermission();
@@ -5355,7 +5354,7 @@
             return ddc.getDozeBrightnessSensorValueToBrightness();
         }
 
-        @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(CONTROL_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public float getDefaultDozeBrightness(int displayId) {
             getDefaultDozeBrightness_enforcePermission();
@@ -6064,6 +6063,7 @@
      * Return the value of the pause
      */
     private static boolean deferDisplayEventsWhenFrozen() {
-        return com.android.server.am.Flags.deferDisplayEventsWhenFrozen();
+        return android.os.Flags.binderFrozenStateChangeCallback()
+                && com.android.server.am.Flags.deferDisplayEventsWhenFrozen();
     }
 }
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 3358f72..5f97410 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -96,7 +96,7 @@
     name: "display_topology"
     namespace: "display_manager"
     description: "Display topology for moving cursors and windows between extended displays"
-    bug: "278199220"
+    bug: "364906028"
     is_fixed_read_only: true
 }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 477660d..4f3aa06 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -492,8 +492,8 @@
         }
 
         if (getCurToken() != null) {
-            removeCurrentToken();
             mService.resetSystemUiLocked(this);
+            removeCurrentToken();
             mAutofillController.onResetSystemUi();
         }
 
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 3528d3d..8a35006 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -487,6 +487,20 @@
     ProviderInfo resolveContentProvider(@NonNull String name,
             @PackageManager.ResolveInfoFlagsBits long flags, @UserIdInt int userId, int callingUid);
 
+    /**
+     * Resolves a ContentProvider on behalf of a UID
+     * @param name Authority of the content provider
+     * @param flags option flags to modify the data returned.
+     * @param userId Current user ID
+     * @param filterCallingUid UID of the caller who's access to the content provider
+     *        is to be checked
+     * @return
+     */
+    @Nullable
+    ProviderInfo resolveContentProviderForUid(@NonNull String name,
+            @PackageManager.ResolveInfoFlagsBits long flags, @UserIdInt int userId,
+            int filterCallingUid);
+
     @Nullable
     ProviderInfo getGrantImplicitAccessProviderInfo(int recipientUid,
             @NonNull String visibleAuthority);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index be2f58d..3861762 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -4749,6 +4749,38 @@
 
     @Nullable
     @Override
+    public ProviderInfo resolveContentProviderForUid(@NonNull String name,
+            @PackageManager.ResolveInfoFlagsBits long flags, @UserIdInt int userId,
+            int filterCallingUid) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.RESOLVE_COMPONENT_FOR_UID,
+                "resolveContentProviderForUid");
+
+        int callingUid = Binder.getCallingUid();
+        int filterUserId = UserHandle.getUserId(filterCallingUid);
+        enforceCrossUserPermission(callingUid, filterUserId, false, false,
+                "resolveContentProviderForUid");
+
+        // Real callingUid should be able to see filterCallingUid
+        if (filterAppAccess(filterCallingUid, callingUid)) {
+            return null;
+        }
+
+        ProviderInfo pInfo = resolveContentProvider(name, flags, userId, filterCallingUid);
+        if (pInfo == null) {
+            return null;
+        }
+        // Real callingUid should be able to see the ContentProvider accessible to filterCallingUid
+        ProviderInfo pInfo2 = resolveContentProvider(name, flags, userId, callingUid);
+        if (pInfo2 != null
+                && Objects.equals(pInfo.name, pInfo2.name)
+                && Objects.equals(pInfo.authority, pInfo2.authority)) {
+            return pInfo;
+        }
+        return null;
+    }
+
+    @Nullable
+    @Override
     public ProviderInfo resolveContentProvider(@NonNull String name,
             @PackageManager.ResolveInfoFlagsBits long flags, @UserIdInt int userId,
             int callingUid) {
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index f05c54d..b11d349 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -1129,6 +1129,12 @@
     }
 
     @Override
+    public final ProviderInfo resolveContentProviderForUid(String name,
+            @PackageManager.ResolveInfoFlagsBits long flags, int userId, int filterCallingUid) {
+        return snapshot().resolveContentProviderForUid(name, flags, userId, filterCallingUid);
+    }
+
+    @Override
     @Deprecated
     public final void resetApplicationPreferences(int userId) {
         mPreferredActivityHelper.resetApplicationPreferences(userId);
diff --git a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
index e9cb279..e989d68 100644
--- a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
+++ b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
@@ -40,7 +40,7 @@
 import com.android.internal.util.function.DodecFunction;
 import com.android.internal.util.function.HexConsumer;
 import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.NonaFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.TriFunction;
 import com.android.internal.util.function.UndecFunction;
@@ -351,22 +351,22 @@
         @Override
         public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
                 @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
-                @Nullable String message, boolean shouldCollectMessage,
-                @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String,
-                        Boolean, SyncNotedAppOp> superImpl) {
+                @Nullable String message, boolean shouldCollectMessage, int notedCount,
+                @NonNull NonaFunction<Integer, Integer, String, String, Integer, Boolean, String,
+                                        Boolean, Integer, SyncNotedAppOp> superImpl) {
             if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
                         Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     return superImpl.apply(code, shellUid, SHELL_PKG, featureId, virtualDeviceId,
-                            shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                            shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
             return superImpl.apply(code, uid, packageName, featureId, virtualDeviceId,
-                    shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                    shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 3f9144f..dea52fd 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -53,7 +53,7 @@
 import com.android.internal.util.function.DodecFunction;
 import com.android.internal.util.function.HexConsumer;
 import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.NonaFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.UndecFunction;
 import com.android.server.LocalServices;
@@ -248,11 +248,12 @@
     public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
             @Nullable String attributionTag, int virtualDeviceId,
             boolean shouldCollectAsyncNotedOp, @Nullable String message,
-            boolean shouldCollectMessage, @NonNull OctFunction<Integer, Integer, String, String,
-                    Integer, Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
+            boolean shouldCollectMessage, int notedCount,
+            @NonNull NonaFunction<Integer, Integer, String, String,
+                    Integer, Boolean, String, Boolean, Integer, SyncNotedAppOp> superImpl) {
         return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag),
                 resolveUid(code, uid), packageName, attributionTag, virtualDeviceId,
-                shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 8346112..a0bc77e 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -24,6 +24,7 @@
 import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
 import static com.android.server.power.hint.Flags.resetOnForkEnabled;
 
+import android.Manifest;
 import android.adpf.ISessionManager;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -59,6 +60,9 @@
 import android.os.ServiceManager;
 import android.os.SessionCreationConfig;
 import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.system.Os;
+import android.system.OsConstants;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -79,7 +83,10 @@
 import com.android.server.power.hint.HintManagerService.AppHintSession.SessionModes;
 import com.android.server.utils.Slogf;
 
+import java.io.BufferedReader;
 import java.io.FileDescriptor;
+import java.io.FileReader;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -95,6 +102,8 @@
 import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /** An hint service implementation that runs in System Server process. */
 public final class HintManagerService extends SystemService {
@@ -103,10 +112,10 @@
 
     private static final int EVENT_CLEAN_UP_UID = 3;
     @VisibleForTesting  static final int CLEAN_UP_UID_DELAY_MILLIS = 1000;
-    // The minimum interval between the headroom calls as rate limiting.
-    private static final int DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS = 1000;
-    private static final int DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS = 1000;
 
+    // example: cpu  2255 34 2290 22625563 6290 127 456
+    private static final Pattern PROC_STAT_CPU_TIME_TOTAL_PATTERN =
+            Pattern.compile("cpu\\s+(?<user>[0-9]+)\\s(?<nice>[0-9]+).+");
 
     @VisibleForTesting final long mHintSessionPreferredRate;
 
@@ -192,10 +201,26 @@
     private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
     private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms";
     private static final String PROPERTY_CHECK_HEADROOM_TID = "persist.hms.check_headroom_tid";
-    private static final String PROPERTY_CHECK_HEADROOM_AFFINITY = "persist.hms.check_affinity";
+    private static final String PROPERTY_CHECK_HEADROOM_AFFINITY =
+            "persist.hms.check_headroom_affinity";
+    private static final String PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS =
+            "persist.hms.check_headroom_proc_stat_min_millis";
     private Boolean mFMQUsesIntegratedEventFlag = false;
 
     private final Object mCpuHeadroomLock = new Object();
+    @VisibleForTesting
+    final float mJiffyMillis;
+    private final int mCheckHeadroomProcStatMinMillis;
+    @GuardedBy("mCpuHeadroomLock")
+    private long mLastCpuUserModeTimeCheckedMillis = 0;
+    @GuardedBy("mCpuHeadroomLock")
+    private long mLastCpuUserModeJiffies = 0;
+    @GuardedBy("mCpuHeadroomLock")
+    private final Map<Integer, Long> mUidToLastUserModeJiffies;
+    @VisibleForTesting
+    private String mProcStatFilePathOverride = null;
+    @VisibleForTesting
+    private boolean mEnforceCpuHeadroomUserModeCpuTimeCheck = false;
 
     private ISessionManager mSessionManager;
 
@@ -310,8 +335,16 @@
                 new GpuHeadroomParamsInternal().calculationWindowMillis;
         if (mSupportInfo.headroom.isCpuSupported) {
             mCpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.cpuMinIntervalMillis);
+            mUidToLastUserModeJiffies = new ArrayMap<>();
+            long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
+            mJiffyMillis = 1000.0f / jiffyHz;
+            mCheckHeadroomProcStatMinMillis = SystemProperties.getInt(
+                    PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS, 50);
         } else {
             mCpuHeadroomCache = null;
+            mUidToLastUserModeJiffies = null;
+            mJiffyMillis = 0.0f;
+            mCheckHeadroomProcStatMinMillis = 0;
         }
         if (mSupportInfo.headroom.isGpuSupported) {
             mGpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.gpuMinIntervalMillis);
@@ -370,6 +403,12 @@
         return supportInfo;
     }
 
+    @VisibleForTesting
+    void setProcStatPathOverride(String override) {
+        mProcStatFilePathOverride = override;
+        mEnforceCpuHeadroomUserModeCpuTimeCheck = true;
+    }
+
     private ServiceThread createCleanUpThread() {
         final ServiceThread handlerThread = new ServiceThread(TAG,
                 Process.THREAD_PRIORITY_LOWEST, true /*allowIo*/);
@@ -851,6 +890,11 @@
                         mChannelMap.remove(uid);
                     }
                 }
+                synchronized (mCpuHeadroomLock) {
+                    if (mSupportInfo.headroom.isCpuSupported && mUidToLastUserModeJiffies != null) {
+                        mUidToLastUserModeJiffies.remove(uid);
+                    }
+                }
             });
         }
 
@@ -1230,7 +1274,7 @@
             // Only call into AM if the tid is either isolated or invalid
             if (isolatedPids == null) {
                 // To avoid deadlock, do not call into AMS if the call is from system.
-                if (uid == Process.SYSTEM_UID) {
+                if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) {
                     return tid;
                 }
                 isolatedPids = mAmInternal.getIsolatedProcesses(uid);
@@ -1485,14 +1529,17 @@
                 throw new UnsupportedOperationException();
             }
             checkCpuHeadroomParams(params);
+            final int uid = Binder.getCallingUid();
+            final int pid = Binder.getCallingPid();
             final CpuHeadroomParams halParams = new CpuHeadroomParams();
-            halParams.tids = new int[]{Binder.getCallingPid()};
+            halParams.tids = new int[]{pid};
             halParams.calculationType = params.calculationType;
             halParams.calculationWindowMillis = params.calculationWindowMillis;
             if (params.usesDeviceHeadroom) {
                 halParams.tids = new int[]{};
             } else if (params.tids != null && params.tids.length > 0) {
-                if (SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_TID, true)) {
+                if (UserHandle.getAppId(uid) != Process.SYSTEM_UID && SystemProperties.getBoolean(
+                        PROPERTY_CHECK_HEADROOM_TID, true)) {
                     final int tgid = Process.getThreadGroupLeader(Binder.getCallingPid());
                     for (int tid : params.tids) {
                         if (Process.getThreadGroupLeader(tid) != tgid) {
@@ -1515,6 +1562,20 @@
                     if (res != null) return res;
                 }
             }
+            final boolean shouldCheckUserModeCpuTime =
+                    mEnforceCpuHeadroomUserModeCpuTimeCheck
+                            || (UserHandle.getAppId(uid) != Process.SYSTEM_UID
+                            && mContext.checkCallingPermission(
+                            Manifest.permission.DEVICE_POWER)
+                            == PackageManager.PERMISSION_DENIED);
+
+            if (shouldCheckUserModeCpuTime) {
+                synchronized (mCpuHeadroomLock) {
+                    if (!checkPerUidUserModeCpuTimeElapsedLocked(uid)) {
+                        return null;
+                    }
+                }
+            }
             // return from HAL directly
             try {
                 final CpuHeadroomResult result = mPowerHal.getCpuHeadroom(halParams);
@@ -1528,6 +1589,11 @@
                         mCpuHeadroomCache.add(halParams, result);
                     }
                 }
+                if (shouldCheckUserModeCpuTime) {
+                    synchronized (mCpuHeadroomLock) {
+                        mUidToLastUserModeJiffies.put(uid, mLastCpuUserModeJiffies);
+                    }
+                }
                 return result;
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to get CPU headroom from Power HAL", e);
@@ -1556,6 +1622,40 @@
             }
         }
 
+        // check if there has been sufficient user mode cpu time elapsed since last call
+        // from the same uid
+        @GuardedBy("mCpuHeadroomLock")
+        private boolean checkPerUidUserModeCpuTimeElapsedLocked(int uid) {
+            // skip checking proc stat if it's within mCheckHeadroomProcStatMinMillis
+            if (System.currentTimeMillis() - mLastCpuUserModeTimeCheckedMillis
+                    > mCheckHeadroomProcStatMinMillis) {
+                try {
+                    mLastCpuUserModeJiffies = getUserModeJiffies();
+                } catch (Exception e) {
+                    Slog.e(TAG, "Failed to get user mode CPU time", e);
+                    return false;
+                }
+                mLastCpuUserModeTimeCheckedMillis = System.currentTimeMillis();
+            }
+            if (mUidToLastUserModeJiffies.containsKey(uid)) {
+                long uidLastUserModeJiffies = mUidToLastUserModeJiffies.get(uid);
+                if ((mLastCpuUserModeJiffies - uidLastUserModeJiffies) * mJiffyMillis
+                        < mSupportInfo.headroom.cpuMinIntervalMillis) {
+                    Slog.w(TAG, "UID " + uid + " is requesting CPU headroom too soon");
+                    Slog.d(TAG, "UID " + uid + " last request at "
+                            + uidLastUserModeJiffies * mJiffyMillis
+                            + "ms with device currently at "
+                            + mLastCpuUserModeJiffies * mJiffyMillis
+                            + "ms, the interval: "
+                            + (mLastCpuUserModeJiffies - uidLastUserModeJiffies)
+                            * mJiffyMillis + "ms is less than require minimum interval "
+                            + mSupportInfo.headroom.cpuMinIntervalMillis + "ms");
+                    return false;
+                }
+            }
+            return true;
+        }
+
         private void checkCpuHeadroomParams(CpuHeadroomParamsInternal params) {
             boolean calculationTypeMatched = false;
             try {
@@ -1731,6 +1831,27 @@
             }
         }
 
+        private long getUserModeJiffies() throws IOException {
+            String filePath =
+                    mProcStatFilePathOverride == null ? "/proc/stat" : mProcStatFilePathOverride;
+            try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    Matcher matcher = PROC_STAT_CPU_TIME_TOTAL_PATTERN.matcher(line.trim());
+                    if (matcher.find()) {
+                        long userJiffies = Long.parseLong(matcher.group("user"));
+                        long niceJiffies = Long.parseLong(matcher.group("nice"));
+                        Slog.d(TAG,
+                                "user: " + userJiffies + " nice: " + niceJiffies
+                                        + " total " + (userJiffies + niceJiffies));
+                        reader.close();
+                        return userJiffies + niceJiffies;
+                    }
+                }
+            }
+            throw new IllegalStateException("Can't find cpu line in " + filePath);
+        }
+
         private boolean checkGraphicsPipelineValid(SessionCreationConfig creationConfig, int uid) {
             if (creationConfig.modesToEnable == null) {
                 return true;
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 1bed48a..2e6be5b 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -1251,12 +1251,12 @@
                 // should document in PackageInstaller.SessionParams#setEnableRollback
                 // After enabling and committing any rollback, observe packages and
                 // prepare to rollback if packages crashes too frequently.
-                mPackageWatchdog.startExplicitHealthCheck(mPackageHealthObserver,
-                        rollback.getPackageNames(), mRollbackLifetimeDurationInMillis);
+                mPackageWatchdog.startExplicitHealthCheck(rollback.getPackageNames(),
+                        mRollbackLifetimeDurationInMillis, mPackageHealthObserver);
             }
         } else {
-            mPackageWatchdog.startExplicitHealthCheck(mPackageHealthObserver,
-                    rollback.getPackageNames(), mRollbackLifetimeDurationInMillis);
+            mPackageWatchdog.startExplicitHealthCheck(rollback.getPackageNames(),
+                    mRollbackLifetimeDurationInMillis, mPackageHealthObserver);
         }
         runExpiration();
     }
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index 19eba5f..90c2216 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -51,8 +51,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.server.wm.utils.InsetUtils;
+import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 /**
  * Base class for a Snapshot controller
@@ -148,43 +151,60 @@
     protected abstract Rect getLetterboxInsets(ActivityRecord topActivity);
 
     /**
-     * This is different than {@link #recordSnapshotInner(TYPE)} because it doesn't store
-     * the snapshot to the cache and returns the TaskSnapshot immediately.
-     *
-     * This is only used for testing so the snapshot content can be verified.
+     * This is different than {@link #recordSnapshotInner(TYPE, boolean, Consumer)}  because it
+     * doesn't store the snapshot to the cache and returns the TaskSnapshot immediately.
      */
     @VisibleForTesting
-    TaskSnapshot captureSnapshot(TYPE source) {
-        final TaskSnapshot snapshot;
+    SnapshotSupplier captureSnapshot(TYPE source, boolean allowAppTheme) {
+        final SnapshotSupplier supplier = new SnapshotSupplier();
         switch (getSnapshotMode(source)) {
-            case SNAPSHOT_MODE_NONE:
-                return null;
             case SNAPSHOT_MODE_APP_THEME:
-                snapshot = drawAppThemeSnapshot(source);
+                Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "drawAppThemeSnapshot");
+                if (Flags.excludeDrawingAppThemeSnapshotFromLock()) {
+                    if (allowAppTheme) {
+                        supplier.setSupplier(drawAppThemeSnapshot(source));
+                    }
+                } else {
+                    final Supplier<TaskSnapshot> original = drawAppThemeSnapshot(source);
+                    final TaskSnapshot snapshot = original != null ? original.get() : null;
+                    supplier.setSnapshot(snapshot);
+                }
+                Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                 break;
             case SNAPSHOT_MODE_REAL:
-                snapshot = snapshot(source);
+                supplier.setSnapshot(snapshot(source));
                 break;
             default:
-                snapshot = null;
                 break;
         }
-        return snapshot;
+        return supplier;
     }
 
-    final TaskSnapshot recordSnapshotInner(TYPE source) {
+    /**
+     * @param allowAppTheme If true, allows to draw app theme snapshot when it's not allowed to take
+     *                      a real screenshot, but create a fake representation of the app.
+     * @param inLockConsumer Extra task to do in WM lock when first get the snapshot object.
+     */
+    final SnapshotSupplier recordSnapshotInner(TYPE source, boolean allowAppTheme,
+            @Nullable Consumer<TaskSnapshot> inLockConsumer) {
         if (shouldDisableSnapshots()) {
             return null;
         }
-        final TaskSnapshot snapshot = captureSnapshot(source);
-        if (snapshot == null) {
-            return null;
-        }
-        mCache.putSnapshot(source, snapshot);
-        return snapshot;
+        final SnapshotSupplier supplier = captureSnapshot(source, allowAppTheme);
+        supplier.setConsumer(t -> {
+            synchronized (mService.mGlobalLock) {
+                if (!source.isAttached()) {
+                    return;
+                }
+                mCache.putSnapshot(source, t);
+                if (inLockConsumer != null) {
+                    inLockConsumer.accept(t);
+                }
+            }
+        });
+        return supplier;
     }
 
-    @VisibleForTesting
     int getSnapshotMode(TYPE source) {
         final int type = source.getActivityType();
         if (type == ACTIVITY_TYPE_RECENTS || type == ACTIVITY_TYPE_DREAM) {
@@ -400,7 +420,7 @@
      * If we are not allowed to take a real screenshot, this attempts to represent the app as best
      * as possible by using the theme's window background.
      */
-    private TaskSnapshot drawAppThemeSnapshot(TYPE source) {
+    private Supplier<TaskSnapshot> drawAppThemeSnapshot(TYPE source) {
         final ActivityRecord topActivity = getTopActivity(source);
         if (topActivity == null) {
             return null;
@@ -432,26 +452,46 @@
         decorPainter.setInsets(systemBarInsets);
         decorPainter.drawDecors(c /* statusBarExcludeFrame */, null /* alreadyDrawFrame */);
         node.end(c);
-        final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
-        if (hwBitmap == null) {
-            return null;
-        }
+
         final Rect contentInsets = new Rect(systemBarInsets);
         final Rect letterboxInsets = getLetterboxInsets(topActivity);
         InsetUtils.addInsets(contentInsets, letterboxInsets);
-        // Note, the app theme snapshot is never translucent because we enforce a non-translucent
-        // color above
-        final TaskSnapshot taskSnapshot = new TaskSnapshot(
-                System.currentTimeMillis() /* id */,
-                SystemClock.elapsedRealtimeNanos() /* captureTime */,
-                topActivity.mActivityComponent, hwBitmap.getHardwareBuffer(),
-                hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation,
-                mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
-                contentInsets, letterboxInsets, false /* isLowResolution */,
-                false /* isRealSnapshot */, source.getWindowingMode(),
-                attrs.insetsFlags.appearance, false /* isTranslucent */, false /* hasImeSurface */,
-                topActivity.getConfiguration().uiMode /* uiMode */);
-        return validateSnapshot(taskSnapshot);
+
+        final TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
+        builder.setIsRealSnapshot(false);
+        builder.setId(System.currentTimeMillis());
+        builder.setContentInsets(contentInsets);
+        builder.setLetterboxInsets(letterboxInsets);
+
+        builder.setTopActivityComponent(topActivity.mActivityComponent);
+        // Note, the app theme snapshot is never translucent because we enforce a
+        // non-translucent color above.
+        builder.setIsTranslucent(false);
+        builder.setWindowingMode(source.getWindowingMode());
+        builder.setAppearance(attrs.insetsFlags.appearance);
+        builder.setUiMode(topActivity.getConfiguration().uiMode);
+
+        builder.setRotation(mainWindow.getWindowConfiguration().getRotation());
+        builder.setOrientation(mainWindow.getConfiguration().orientation);
+        builder.setTaskSize(new Point(taskWidth, taskHeight));
+        builder.setCaptureTime(SystemClock.elapsedRealtimeNanos());
+
+        return () -> {
+            try {
+                Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "drawAppThemeSnapshot_acquire");
+                // Do not hold WM lock when calling to render thread.
+                final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width,
+                        height);
+                if (hwBitmap == null) {
+                    return null;
+                }
+                builder.setSnapshot(hwBitmap.getHardwareBuffer());
+                builder.setColorSpace(hwBitmap.getColorSpace());
+                return validateSnapshot(builder.build());
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+            }
+        };
     }
 
     static Rect getSystemBarInsets(Rect frame, InsetsState state) {
@@ -482,4 +522,45 @@
         pw.println(prefix + "mSnapshotEnabled=" + mSnapshotEnabled);
         mCache.dump(pw, prefix);
     }
+
+    static class SnapshotSupplier implements Supplier<TaskSnapshot> {
+
+        private TaskSnapshot mSnapshot;
+        private boolean mHasSet;
+        private Consumer<TaskSnapshot> mConsumer;
+        private Supplier<TaskSnapshot> mSupplier;
+
+        /** Callback when the snapshot is get for the first time. */
+        void setConsumer(@NonNull Consumer<TaskSnapshot> consumer) {
+            mConsumer = consumer;
+        }
+
+        void setSupplier(@NonNull Supplier<TaskSnapshot> createSupplier) {
+            mSupplier = createSupplier;
+        }
+
+        void setSnapshot(TaskSnapshot snapshot) {
+            mSnapshot = snapshot;
+        }
+
+        void handleSnapshot() {
+            if (mHasSet) {
+                return;
+            }
+            mHasSet = true;
+            if (mSnapshot == null) {
+                mSnapshot = mSupplier != null ? mSupplier.get() : null;
+            }
+            if (mConsumer != null && mSnapshot != null) {
+                mConsumer.accept(mSnapshot);
+            }
+        }
+
+        @Override
+        @Nullable
+        public TaskSnapshot get() {
+            handleSnapshot();
+            return mSnapshot;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 83b273c..b71256d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3227,7 +3227,7 @@
             return false;
         }
         // If the user preference respects aspect ratio, then it becomes non-resizable.
-        return mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
+        return mAppCompatController.getAppCompatAspectRatioOverrides()
                 .userPreferenceCompatibleWithNonResizability();
     }
 
@@ -3898,11 +3898,18 @@
         final TaskFragment taskFragment = getTaskFragment();
         if (next != null && taskFragment != null && taskFragment.isEmbedded()) {
             final TaskFragment organized = taskFragment.getOrganizedTaskFragment();
-            final TaskFragment adjacent =
-                    organized != null ? organized.getAdjacentTaskFragment() : null;
-            if (adjacent != null && next.isDescendantOf(adjacent)
-                    && organized.topRunningActivity() == null) {
-                delayRemoval = organized.isDelayLastActivityRemoval();
+            if (Flags.allowMultipleAdjacentTaskFragments()) {
+                delayRemoval = organized != null
+                        && organized.topRunningActivity() == null
+                        && organized.isDelayLastActivityRemoval()
+                        && organized.forOtherAdjacentTaskFragments(next::isDescendantOf);
+            } else {
+                final TaskFragment adjacent =
+                        organized != null ? organized.getAdjacentTaskFragment() : null;
+                if (adjacent != null && next.isDescendantOf(adjacent)
+                        && organized.topRunningActivity() == null) {
+                    delayRemoval = organized.isDelayLastActivityRemoval();
+                }
             }
         }
 
@@ -4880,15 +4887,25 @@
      *  @see #canShowWhenLocked(ActivityRecord)
      */
     boolean canShowWhenLocked() {
-        final TaskFragment taskFragment = getTaskFragment();
-        if (taskFragment != null && taskFragment.getAdjacentTaskFragment() != null
-                && taskFragment.isEmbedded()) {
-            final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
-            final ActivityRecord r = adjacentTaskFragment.getTopNonFinishingActivity();
-            return canShowWhenLocked(this) && canShowWhenLocked(r);
-        } else {
-            return canShowWhenLocked(this);
+        if (!canShowWhenLocked(this)) {
+            return false;
         }
+        final TaskFragment taskFragment = getTaskFragment();
+        if (taskFragment == null || !taskFragment.hasAdjacentTaskFragment()
+                || !taskFragment.isEmbedded()) {
+            // No embedded adjacent that need to be checked.
+            return true;
+        }
+
+        // Make sure the embedded adjacent can also be shown.
+        if (!Flags.allowMultipleAdjacentTaskFragments()) {
+            final ActivityRecord adjacentActivity = taskFragment.getAdjacentTaskFragment()
+                    .getTopNonFinishingActivity();
+            return canShowWhenLocked(adjacentActivity);
+        }
+        final boolean hasAdjacentNotAllowToShow = taskFragment.forOtherAdjacentTaskFragments(
+                adjacentTF -> !canShowWhenLocked(adjacentTF.getTopNonFinishingActivity()));
+        return !hasAdjacentNotAllowToShow;
     }
 
     /**
@@ -8528,8 +8545,7 @@
         // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
         // are already calculated in resolveFixedOrientationConfiguration.
         // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
-        if (Flags.immersiveAppRepositioning()
-                && !mAppCompatController.getAppCompatAspectRatioPolicy()
+        if (!mAppCompatController.getAppCompatAspectRatioPolicy()
                     .isLetterboxedForFixedOrientationAndAspectRatio()
                 && !mAppCompatController.getAppCompatAspectRatioOverrides()
                     .hasFullscreenOverride()) {
@@ -8551,18 +8567,6 @@
                 computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
             }
         }
-        // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
-        // are already calculated in resolveFixedOrientationConfiguration, or if in size compat
-        // mode, it should already be calculated in resolveSizeCompatModeConfiguration.
-        // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
-        if (!Flags.immersiveAppRepositioning()
-                && !mAppCompatController.getAppCompatAspectRatioPolicy()
-                    .isLetterboxedForFixedOrientationAndAspectRatio()
-                && !scmPolicy.isInSizeCompatModeForBounds()
-                && !mAppCompatController.getAppCompatAspectRatioOverrides()
-                    .hasFullscreenOverride()) {
-            resolveAspectRatioRestriction(newParentConfiguration);
-        }
 
         if (isFixedOrientationLetterboxAllowed
                 || scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
@@ -8819,9 +8823,6 @@
     }
 
     boolean isImmersiveMode(@NonNull Rect parentBounds) {
-        if (!Flags.immersiveAppRepositioning()) {
-            return false;
-        }
         if (!mResolveConfigHint.mUseOverrideInsetsForConfig
                 && mWmService.mFlags.mInsetsDecoupledConfiguration) {
             return false;
@@ -10355,7 +10356,9 @@
         }
         if (!isVisibleRequested()) {
             // TODO(b/294925498): Remove this finishing check once we have accurate ready tracking.
-            if (task != null && task.getPausingActivity() == this) {
+            if (task != null && task.getPausingActivity() == this
+                    // Display is asleep, so nothing will be visible anyways.
+                    && !mDisplayContent.isSleeping()) {
                 // Visibility of starting activities isn't calculated until pause-complete, so if
                 // this is not paused yet, don't consider it ready.
                 return false;
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index ed8b689..597f75a 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -115,8 +115,8 @@
     private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
             @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
         return mWmService.mAppCompatConfiguration.isCameraCompatRefreshEnabled()
-                && activity.mAppCompatController.getAppCompatOverrides()
-                    .getAppCompatCameraOverrides().shouldRefreshActivityForCameraCompat()
+                && activity.mAppCompatController.getAppCompatCameraOverrides()
+                    .shouldRefreshActivityForCameraCompat()
                 && ArrayUtils.find(mEvaluators.toArray(), evaluator ->
                 ((Evaluator) evaluator)
                         .shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null;
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 9aaa0e1..cfd3248 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -38,6 +38,7 @@
 import java.io.File;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.function.Supplier;
 
 /**
  * When an app token becomes invisible, we take a snapshot (bitmap) and put it into our cache.
@@ -355,7 +356,9 @@
         final int[] mixedCode = new int[size];
         if (size == 1) {
             final ActivityRecord singleActivity = activity.get(0);
-            final TaskSnapshot snapshot = recordSnapshotInner(singleActivity);
+            final Supplier<TaskSnapshot> supplier = recordSnapshotInner(singleActivity,
+                    false /* allowAppTheme */, null /* inLockConsumer */);
+            final TaskSnapshot snapshot = supplier != null ? supplier.get() : null;
             if (snapshot != null) {
                 mixedCode[0] = getSystemHashCode(singleActivity);
                 addUserSavedFile(singleActivity.mUserId, snapshot, mixedCode);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5eee8ec..290f155 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -314,6 +314,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Supplier;
 
 /**
  * System service for managing activities and their containers (task, displays,... ).
@@ -4038,6 +4039,7 @@
         mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "takeTaskSnapshot()");
         final long ident = Binder.clearCallingIdentity();
         try {
+            final Supplier<TaskSnapshot> supplier;
             synchronized (mGlobalLock) {
                 final Task task = mRootWindowContainer.anyTaskForId(taskId,
                         MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
@@ -4050,11 +4052,13 @@
                 // be retrieved by recents. While if updateCache is false, the real snapshot will
                 // always be taken and the snapshot won't be put into SnapshotPersister.
                 if (updateCache) {
-                    return mWindowManager.mTaskSnapshotController.recordSnapshot(task);
+                    supplier = mWindowManager.mTaskSnapshotController
+                            .getRecordSnapshotSupplier(task);
                 } else {
                     return mWindowManager.mTaskSnapshotController.snapshot(task);
                 }
             }
+            return supplier != null ? supplier.get() : null;
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6403,6 +6407,7 @@
         @Override
         public boolean shuttingDown(boolean booted, int timeout) {
             mShuttingDown = true;
+            mWindowManager.mSnapshotController.mTaskSnapshotController.prepareShutdown();
             synchronized (mGlobalLock) {
                 mRootWindowContainer.prepareForShutdown();
                 updateEventDispatchingLocked(booted);
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index e8eae4f..6a0de98 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN;
 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
@@ -36,6 +37,8 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 
+import com.android.window.flags.Flags;
+
 /**
  * Encapsulate app compat policy logic related to aspect ratio.
  */
@@ -239,7 +242,14 @@
                 || AppCompatUtils.isInVrUiMode(mActivityRecord.getConfiguration())
                 // TODO(b/232898850): Always respect aspect ratio requests.
                 // Don't set aspect ratio for activity in ActivityEmbedding split.
-                || (organizedTf != null && !organizedTf.fillsParent())) {
+                || (organizedTf != null && !organizedTf.fillsParent())
+                // Don't set aspect ratio for resizeable activities in freeform.
+                // {@link ActivityRecord#shouldCreateAppCompatDisplayInsets()} will be false for
+                // both activities that are naturally resizeable and activities that have been
+                // forced resizeable.
+                || (Flags.ignoreAspectRatioRestrictionsForResizeableFreeformActivities()
+                    && task.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                    && !mActivityRecord.shouldCreateAppCompatDisplayInsets())) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 330283f..4433d64 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -123,11 +123,6 @@
     }
 
     @NonNull
-    AppCompatOverrides getAppCompatOverrides() {
-        return mAppCompatOverrides;
-    }
-
-    @NonNull
     AppCompatOrientationOverrides getAppCompatOrientationOverrides() {
         return mAppCompatOverrides.getAppCompatOrientationOverrides();
     }
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index 4e390df..e929fb4 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -281,7 +281,6 @@
                         mActivityRecord.mWmService.mTransactionFactory,
                         reachabilityPolicy, letterboxOverrides,
                         this::getLetterboxParentSurface);
-                mLetterbox.attachInput(w);
                 mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
                         .setLetterboxInnerBoundsSupplier(mLetterbox::getInnerFrame);
             }
@@ -335,7 +334,7 @@
             }
             start(winHint);
             if (isRunning() && mLetterbox.needsApplySurfaceChanges()) {
-                mLetterbox.applySurfaceChanges(t, inputT);
+                mLetterbox.applySurfaceChanges(t, inputT, winHint);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
index caff96b..4fac81b 100644
--- a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
@@ -35,8 +35,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 
-import com.android.window.flags.Flags;
-
 /**
  * Encapsulate overrides and configurations about app compat reachability.
  */
@@ -157,33 +155,27 @@
     }
 
     /**
-     * @return {@value true} if the vertical reachability should be allowed in case of
+     * @return {@code true} if the vertical reachability should be allowed in case of
      * thin letterboxing.
      */
     boolean allowVerticalReachabilityForThinLetterbox() {
-        if (!Flags.disableThinLetterboxingPolicy()) {
-            return true;
-        }
         // When the flag is enabled we allow vertical reachability only if the
         // app is not thin letterboxed vertically.
         return !isVerticalThinLetterboxed();
     }
 
     /**
-     * @return {@value true} if the horizontal reachability should be enabled in case of
+     * @return {@code true} if the horizontal reachability should be enabled in case of
      * thin letterboxing.
      */
     boolean allowHorizontalReachabilityForThinLetterbox() {
-        if (!Flags.disableThinLetterboxingPolicy()) {
-            return true;
-        }
         // When the flag is enabled we allow horizontal reachability only if the
         // app is not thin pillarboxed.
         return !isHorizontalThinLetterboxed();
     }
 
     /**
-     * @return {@value true} if the resulting app is letterboxed in a way defined as thin.
+     * @return {@code true} if the resulting app is letterboxed in a way defined as thin.
      */
     boolean isVerticalThinLetterboxed() {
         final int thinHeight = mAppCompatConfiguration.getThinLetterboxHeightPx();
@@ -200,7 +192,7 @@
     }
 
     /**
-     * @return {@value true} if the resulting app is pillarboxed in a way defined as thin.
+     * @return {@code true} if the resulting app is pillarboxed in a way defined as thin.
      */
     boolean isHorizontalThinLetterboxed() {
         final int thinWidth = mAppCompatConfiguration.getThinLetterboxWidthPx();
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index f3b043b..d278dc3 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -28,8 +28,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 
-import com.android.window.flags.Flags;
-
 import java.io.PrintWriter;
 import java.util.function.DoubleSupplier;
 
@@ -202,9 +200,7 @@
         // saved here before resolved bounds are overridden below.
         final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivityRecord.mAppCompatController
                 .getAppCompatAspectRatioPolicy();
-        final boolean useResolvedBounds = Flags.immersiveAppRepositioning()
-                ? aspectRatioPolicy.isAspectRatioApplied()
-                : aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio();
+        final boolean useResolvedBounds = aspectRatioPolicy.isAspectRatioApplied();
         final Rect containerBounds = useResolvedBounds
                 ? new Rect(resolvedBounds)
                 : newParentConfiguration.windowConfiguration.getBounds();
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 1a7c6b7..fc0df64 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -111,9 +111,7 @@
     }
 
     void onEmbeddedWindowGestureTransferred(@NonNull WindowState host) {
-        if (Flags.disallowAppProgressEmbeddedWindow()) {
-            mNavigationMonitor.onEmbeddedWindowGestureTransferred(host);
-        }
+        mNavigationMonitor.onEmbeddedWindowGestureTransferred(host);
     }
 
     /**
@@ -215,7 +213,7 @@
                 infoBuilder.setFocusedTaskId(currentTask.mTaskId);
             }
             boolean transferGestureToEmbedded = false;
-            if (Flags.disallowAppProgressEmbeddedWindow() && embeddedWindows != null) {
+            if (embeddedWindows != null) {
                 for (int i = embeddedWindows.size() - 1; i >= 0; --i) {
                     if (embeddedWindows.get(i).mGestureToEmbedded) {
                         transferGestureToEmbedded = true;
@@ -997,11 +995,9 @@
     /**
      * Handle the pending animation when the running transition finished, all the visibility change
      * has applied so ready to start pending predictive back animation.
-     * @param targets The final animation targets derived in transition.
      * @param finishedTransition The finished transition target.
     */
-    void onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
-            @NonNull Transition finishedTransition) {
+    void onTransitionFinish(@NonNull Transition finishedTransition) {
         if (isMonitoringPrepareTransition(finishedTransition)) {
             if (mAnimationHandler.mPrepareCloseTransition == null) {
                 clearBackAnimations(true /* cancel */);
@@ -1049,14 +1045,6 @@
             return;
         }
 
-        // Ensure the final animation targets which hidden by transition could be visible.
-        for (int i = 0; i < targets.size(); i++) {
-            final WindowContainer wc = targets.get(i).mContainer;
-            if (wc.mSurfaceControl != null) {
-                wc.prepareSurfaces();
-            }
-        }
-
         // The pending builder could be cleared due to prepareSurfaces
         // => updateNonSystemOverlayWindowsVisibilityIfNeeded
         // => setForceHideNonSystemOverlayWindowIfNeeded
diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java
index 3aa3558..0027992 100644
--- a/services/core/java/com/android/server/wm/CameraStateMonitor.java
+++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java
@@ -110,8 +110,10 @@
     }
 
     void startListeningToCameraState() {
-        mCameraManager.registerAvailabilityCallback(
-                mWmService.mContext.getMainExecutor(), mAvailabilityCallback);
+        if (mCameraManager != null) {
+            mCameraManager.registerAvailabilityCallback(
+                    mWmService.mContext.getMainExecutor(), mAvailabilityCallback);
+        }
         mIsRunning = true;
     }
 
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 258a87e..3c60d82 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -289,7 +289,8 @@
                 transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
                 transaction.show(surfaceControl);
                 displayContent.reparentToOverlay(transaction, surfaceControl);
-                mDragState.updateDragSurfaceLocked(true, touchX, touchY);
+                mDragState.updateDragSurfaceLocked(true /* keepHandling */,
+                        displayContent.getDisplayId(), touchX, touchY);
                 if (SHOW_LIGHT_TRANSACTIONS) {
                     Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
                 }
@@ -483,10 +484,11 @@
      * Handles motion events.
      * @param keepHandling Whether if the drag operation is continuing or this is the last motion
      *          event.
+     * @param displayId id of the display the X,Y coordinate is n.
      * @param newX X coordinate value in dp in the screen coordinate
      * @param newY Y coordinate value in dp in the screen coordinate
      */
-    void handleMotionEvent(boolean keepHandling, float newX, float newY) {
+    void handleMotionEvent(boolean keepHandling, int displayId, float newX, float newY) {
         synchronized (mService.mGlobalLock) {
             if (!dragDropActiveLocked()) {
                 // The drag has ended but the clean-up message has not been processed by
@@ -495,7 +497,7 @@
                 return;
             }
 
-            mDragState.updateDragSurfaceLocked(keepHandling, newX, newY);
+            mDragState.updateDragSurfaceLocked(keepHandling, displayId, newX, newY);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DragInputEventReceiver.java b/services/core/java/com/android/server/wm/DragInputEventReceiver.java
index 5372d8b..8f4548f 100644
--- a/services/core/java/com/android/server/wm/DragInputEventReceiver.java
+++ b/services/core/java/com/android/server/wm/DragInputEventReceiver.java
@@ -22,13 +22,13 @@
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.BUTTON_STYLUS_PRIMARY;
+
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.os.Looper;
 import android.util.Slog;
 import android.view.InputChannel;
-import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.MotionEvent;
@@ -63,6 +63,7 @@
                 return;
             }
             final MotionEvent motionEvent = (MotionEvent) event;
+            final int displayId = motionEvent.getDisplayId();
             final float newX = motionEvent.getRawX();
             final float newY = motionEvent.getRawY();
             final boolean isStylusButtonDown =
@@ -102,7 +103,8 @@
                     return;
             }
 
-            mDragDropController.handleMotionEvent(!mMuteInput /* keepHandling */, newX, newY);
+            mDragDropController.handleMotionEvent(!mMuteInput /* keepHandling */, displayId, newX,
+                    newY);
             handled = true;
         } catch (Exception e) {
             Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 1c4e487..3a0e41a 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -113,8 +113,8 @@
     boolean mRelinquishDragSurfaceToDropTarget;
     float mAnimatedScale = 1.0f;
     float mOriginalAlpha;
-    float mOriginalX, mOriginalY;
-    float mCurrentX, mCurrentY;
+    float mOriginalDisplayX, mOriginalDisplayY;
+    float mCurrentDisplayX, mCurrentDisplayY;
     float mThumbOffsetX, mThumbOffsetY;
     InputInterceptor mInputInterceptor;
     ArrayList<WindowState> mNotifiedWindows;
@@ -230,22 +230,22 @@
         if (mDragInProgress) {
             if (DEBUG_DRAG) Slog.d(TAG_WM, "Broadcasting DRAG_ENDED");
             for (WindowState ws : mNotifiedWindows) {
-                float x = 0;
-                float y = 0;
+                float inWindowX = 0;
+                float inWindowY = 0;
                 SurfaceControl dragSurface = null;
                 if (!mDragResult && (ws.mSession.mPid == mPid)) {
                     // Report unconsumed drop location back to the app that started the drag.
-                    x = ws.translateToWindowX(mCurrentX);
-                    y = ws.translateToWindowY(mCurrentY);
+                    inWindowX = ws.translateToWindowX(mCurrentDisplayX);
+                    inWindowY = ws.translateToWindowY(mCurrentDisplayY);
                     if (relinquishDragSurfaceToDragSource()) {
                         // If requested (and allowed), report the drag surface back to the app
                         // starting the drag to handle the return animation
                         dragSurface = mSurfaceControl;
                     }
                 }
-                DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, x, y,
-                        mThumbOffsetX, mThumbOffsetY, mFlags, null, null, null, dragSurface, null,
-                        mDragResult);
+                DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, inWindowX,
+                        inWindowY, mThumbOffsetX, mThumbOffsetY, mFlags, null, null, null,
+                        dragSurface, null, mDragResult);
                 try {
                     if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DRAG_ENDED to " + ws);
                     ws.mClient.dispatchDragEvent(event);
@@ -297,70 +297,71 @@
     }
 
     /**
-     * Creates the drop event for this drag gesture.  If `touchedWin` is null, then the drop event
-     * will be created for dispatching to the unhandled drag and the drag surface will be provided
-     * as a part of the dispatched event.
+     * Creates the drop event for dispatching to the unhandled drag.
+     * TODO(b/384841906): Update `inWindowX` and `inWindowY` to be display-coordinate.
      */
-    private DragEvent createDropEvent(float x, float y, @Nullable WindowState touchedWin,
-            boolean includePrivateInfo) {
-        if (touchedWin != null) {
-            final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
-            final DragAndDropPermissionsHandler dragAndDropPermissions;
-            if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
-                    && mData != null) {
-                dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock,
-                        mData,
-                        mUid,
-                        touchedWin.getOwningPackage(),
-                        mFlags & DRAG_FLAGS_URI_PERMISSIONS,
-                        mSourceUserId,
-                        targetUserId);
-            } else {
-                dragAndDropPermissions = null;
-            }
-            if (mSourceUserId != targetUserId) {
-                if (mData != null) {
-                    mData.fixUris(mSourceUserId);
-                }
-            }
-            final boolean targetInterceptsGlobalDrag = targetInterceptsGlobalDrag(touchedWin);
-            return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mDataDescription, mData,
-                    /* includeDragSurface= */ targetInterceptsGlobalDrag,
-                    /* includeDragFlags= */ targetInterceptsGlobalDrag,
-                    dragAndDropPermissions);
+    private DragEvent createUnhandledDropEvent(float inWindowX, float inWindowY) {
+        return obtainDragEvent(DragEvent.ACTION_DROP, inWindowX, inWindowY, mDataDescription, mData,
+                /* includeDragSurface= */ true,
+                /* includeDragFlags= */ true, null /* dragAndDropPermissions */);
+    }
+
+    /**
+     * Creates the drop event for this drag gesture.
+     */
+    private DragEvent createDropEvent(float inWindowX, float inWindowY, WindowState touchedWin) {
+        final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
+        final DragAndDropPermissionsHandler dragAndDropPermissions;
+        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
+                && mData != null) {
+            dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock, mData,
+                    mUid, touchedWin.getOwningPackage(), mFlags & DRAG_FLAGS_URI_PERMISSIONS,
+                    mSourceUserId, targetUserId);
         } else {
-            return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mDataDescription, mData,
-                    /* includeDragSurface= */ includePrivateInfo,
-                    /* includeDragFlags= */ includePrivateInfo,
-                    null /* dragAndDropPermissions */);
+            dragAndDropPermissions = null;
         }
+        if (mSourceUserId != targetUserId) {
+            if (mData != null) {
+                mData.fixUris(mSourceUserId);
+            }
+        }
+        final boolean targetInterceptsGlobalDrag = targetInterceptsGlobalDrag(touchedWin);
+        return obtainDragEvent(DragEvent.ACTION_DROP, inWindowX, inWindowY, mDataDescription, mData,
+                /* includeDragSurface= */ targetInterceptsGlobalDrag,
+                /* includeDragFlags= */ targetInterceptsGlobalDrag, dragAndDropPermissions);
     }
 
     /**
      * Notify the drop target and tells it about the data. If the drop event is not sent to the
      * target, invokes {@code endDragLocked} after the unhandled drag listener gets a chance to
      * handle the drop.
+     * @param inWindowX if `token` refers to a dragEvent-accepting window, `inWindowX` will be
+     *                  inside the window, else values might be invalid (0, 0).
+     * @param inWindowY if `token` refers to a dragEvent-accepting window, `inWindowY` will be
+     *                  inside the window, else values might be invalid (0, 0).
      */
-    boolean reportDropWindowLock(IBinder token, float x, float y) {
+    boolean reportDropWindowLock(IBinder token, float inWindowX, float inWindowY) {
         if (mAnimator != null) {
             return false;
         }
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DROP");
-            return reportDropWindowLockInner(token, x, y);
+            return reportDropWindowLockInner(token, inWindowX, inWindowY);
         } finally {
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 
-    private boolean reportDropWindowLockInner(IBinder token, float x, float y) {
+    private boolean reportDropWindowLockInner(IBinder token, float inWindowX, float inWindowY) {
         if (mAnimator != null) {
             return false;
         }
 
         final WindowState touchedWin = mService.mInputToWindowMap.get(token);
-        final DragEvent unhandledDropEvent = createDropEvent(x, y, null /* touchedWin */,
-                true /* includePrivateInfo */);
+        // TODO(b/384841906): The x, y here when sent to a window and unhandled, will still be
+        //  relative to the window it was originally sent to. Need to update this to actually be
+        //  display-coordinate.
+        final DragEvent unhandledDropEvent = createUnhandledDropEvent(inWindowX, inWindowY);
         if (!isWindowNotified(touchedWin)) {
             // Delegate to the unhandled drag listener as a first pass
             if (mDragDropController.notifyUnhandledDrop(unhandledDropEvent, "unhandled-drop")) {
@@ -381,7 +382,7 @@
         if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to " + touchedWin);
 
         final IBinder clientToken = touchedWin.mClient.asBinder();
-        final DragEvent event = createDropEvent(x, y, touchedWin, false /* includePrivateInfo */);
+        final DragEvent event = createDropEvent(inWindowX, inWindowY, touchedWin);
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DragDropController#dispatchDrop");
             touchedWin.mClient.dispatchDragEvent(event);
@@ -486,8 +487,8 @@
      */
     void broadcastDragStartedLocked(final float touchX, final float touchY) {
         Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_STARTED");
-        mOriginalX = mCurrentX = touchX;
-        mOriginalY = mCurrentY = touchY;
+        mOriginalDisplayX = mCurrentDisplayX = touchX;
+        mOriginalDisplayY = mCurrentDisplayY = touchY;
 
         // Cache a base-class instance of the clip metadata so that parceling
         // works correctly in calling out to the apps.
@@ -636,7 +637,7 @@
             if (isWindowNotified(newWin)) {
                 return;
             }
-            sendDragStartedLocked(newWin, mCurrentX, mCurrentY,
+            sendDragStartedLocked(newWin, mCurrentDisplayX, mCurrentDisplayY,
                     containsApplicationExtras(mDataDescription));
         }
     }
@@ -685,12 +686,21 @@
         mAnimator = createCancelAnimationLocked();
     }
 
-    void updateDragSurfaceLocked(boolean keepHandling, float x, float y) {
+    /**
+     * Updates the position of the drag surface.
+     *
+     * @param keepHandling whether to keep handling the drag.
+     * @param displayId the display ID of the drag surface.
+     * @param displayX the x-coordinate of the drag surface in the display's coordinate frame.
+     * @param displayY the y-coordinate of the drag surface in the display's coordinate frame.
+     */
+    void updateDragSurfaceLocked(boolean keepHandling, int displayId, float displayX,
+            float displayY) {
         if (mAnimator != null) {
             return;
         }
-        mCurrentX = x;
-        mCurrentY = y;
+        mCurrentDisplayX = displayX;
+        mCurrentDisplayY = displayY;
 
         if (!keepHandling) {
             return;
@@ -700,9 +710,10 @@
         if (SHOW_LIGHT_TRANSACTIONS) {
             Slog.i(TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
         }
-        mTransaction.setPosition(mSurfaceControl, x - mThumbOffsetX, y - mThumbOffsetY).apply();
-        ProtoLog.i(WM_SHOW_TRANSACTIONS, "DRAG %s: pos=(%d,%d)", mSurfaceControl,
-                (int) (x - mThumbOffsetX), (int) (y - mThumbOffsetY));
+        mTransaction.setPosition(mSurfaceControl, displayX - mThumbOffsetX,
+                displayY - mThumbOffsetY).apply();
+        ProtoLog.i(WM_SHOW_TRANSACTIONS, "DRAG %s: displayId=%d, pos=(%d,%d)", mSurfaceControl,
+                displayId, (int) (displayX - mThumbOffsetX), (int) (displayY - mThumbOffsetY));
     }
 
     /**
@@ -713,6 +724,12 @@
         return mDragInProgress;
     }
 
+    /**
+     * `x` and `y` here varies between local window coordinate, relative coordinate to another
+     * window and local display coordinate, all depending on the `action`. Please take a look
+     * at the callers to determine the type.
+     * TODO(b/384845022): Properly document the events sent based on the event type.
+     */
     private DragEvent obtainDragEvent(int action, float x, float y, ClipDescription description,
             ClipData data, boolean includeDragSurface, boolean includeDragFlags,
             IDragAndDropPermissions dragAndDropPermissions) {
@@ -728,34 +745,34 @@
         final long duration;
         if (mCallingTaskIdToHide != -1) {
             animator = ValueAnimator.ofPropertyValuesHolder(
-                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, mCurrentX, mCurrentX),
-                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentY, mCurrentY),
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, mCurrentDisplayX,
+                            mCurrentDisplayX),
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentDisplayY,
+                            mCurrentDisplayY),
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
                             mAnimatedScale),
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
             duration = MIN_ANIMATION_DURATION_MS;
         } else {
             animator = ValueAnimator.ofPropertyValuesHolder(
-                    PropertyValuesHolder.ofFloat(
-                            ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX,
-                            mOriginalX - mThumbOffsetX),
-                    PropertyValuesHolder.ofFloat(
-                            ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY,
-                            mOriginalY - mThumbOffsetY),
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
+                            mCurrentDisplayX - mThumbOffsetX, mOriginalDisplayX - mThumbOffsetX),
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
+                            mCurrentDisplayY - mThumbOffsetY, mOriginalDisplayY - mThumbOffsetY),
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
                             mAnimatedScale),
-                    PropertyValuesHolder.ofFloat(
-                            ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2));
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha,
+                            mOriginalAlpha / 2));
 
-            final float translateX = mOriginalX - mCurrentX;
-            final float translateY = mOriginalY - mCurrentY;
+            final float translateX = mOriginalDisplayX - mCurrentDisplayX;
+            final float translateY = mOriginalDisplayY - mCurrentDisplayY;
             // Adjust the duration to the travel distance.
             final double travelDistance = Math.sqrt(
                     translateX * translateX + translateY * translateY);
-            final double displayDiagonal =
-                    Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
-            duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
-                    * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
+            final double displayDiagonal = Math.sqrt(
+                    mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
+            duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal * (
+                    MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
         }
 
         final AnimationListener listener = new AnimationListener();
@@ -771,18 +788,20 @@
     private ValueAnimator createCancelAnimationLocked() {
         final ValueAnimator animator;
         if (mCallingTaskIdToHide != -1) {
-             animator = ValueAnimator.ofPropertyValuesHolder(
-                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, mCurrentX, mCurrentX),
-                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentY, mCurrentY),
+            animator = ValueAnimator.ofPropertyValuesHolder(
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, mCurrentDisplayX,
+                            mCurrentDisplayX),
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentDisplayY,
+                            mCurrentDisplayY),
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
                             mAnimatedScale),
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
         } else {
             animator = ValueAnimator.ofPropertyValuesHolder(
-                    PropertyValuesHolder.ofFloat(
-                            ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX),
-                    PropertyValuesHolder.ofFloat(
-                            ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY),
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
+                            mCurrentDisplayX - mThumbOffsetX, mCurrentDisplayX),
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
+                            mCurrentDisplayY - mThumbOffsetY, mCurrentDisplayY),
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, 0),
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
         }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index cf145f9..ce85184 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -374,12 +374,6 @@
     void notifyControlChanged(InsetsControlTarget target, InsetsSourceProvider provider) {
         addToPendingControlMaps(target, provider);
         notifyPendingInsetsControlChanged();
-
-        if (android.view.inputmethod.Flags.refactorInsetsController()) {
-            notifyInsetsChanged();
-            mDisplayContent.updateSystemGestureExclusion();
-            mDisplayContent.getDisplayPolicy().updateSystemBarAttributes();
-        }
     }
 
     void notifySurfaceTransactionReady(InsetsSourceProvider provider, long id, boolean ready) {
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index ca47133..29c0c7b 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -22,6 +22,7 @@
 import static android.window.TaskConstants.TASK_CHILD_LAYER_TASK_OVERLAY;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -174,11 +175,12 @@
     public void destroy() {
         mOuter.setEmpty();
         mInner.setEmpty();
-
+        final SurfaceControl.Transaction tx = mTransactionFactory.get();
         for (LetterboxSurface surface : mSurfaces) {
-            surface.remove();
+            surface.remove(tx);
         }
-        mFullWindowSurface.remove();
+        mFullWindowSurface.remove(tx);
+        tx.apply();
     }
 
     /** Returns whether a call to {@link #applySurfaceChanges} would change the surface. */
@@ -196,30 +198,19 @@
 
     /** Applies surface changes such as colour, window crop, position and input info. */
     public void applySurfaceChanges(@NonNull SurfaceControl.Transaction t,
-            @NonNull SurfaceControl.Transaction inputT) {
+            @NonNull SurfaceControl.Transaction inputT, @NonNull WindowState windowState) {
         if (useFullWindowSurface()) {
+            for (LetterboxSurface surface : mSurfaces) {
+                surface.remove(t);
+            }
+            mFullWindowSurface.attachInput(windowState);
             mFullWindowSurface.applySurfaceChanges(t, inputT);
-
-            for (LetterboxSurface surface : mSurfaces) {
-                surface.remove();
-            }
         } else {
+            mFullWindowSurface.remove(t);
             for (LetterboxSurface surface : mSurfaces) {
+                surface.attachInput(windowState);
                 surface.applySurfaceChanges(t, inputT);
             }
-
-            mFullWindowSurface.remove();
-        }
-    }
-
-    /** Enables touches to slide into other neighboring surfaces. */
-    void attachInput(WindowState win) {
-        if (useFullWindowSurface()) {
-            mFullWindowSurface.attachInput(win);
-        } else {
-            for (LetterboxSurface surface : mSurfaces) {
-                surface.attachInput(win);
-            }
         }
     }
 
@@ -358,9 +349,10 @@
         private final Rect mLayoutFrameGlobal = new Rect();
         private final Rect mLayoutFrameRelative = new Rect();
 
+        @Nullable
         private InputInterceptor mInputInterceptor;
 
-        public LetterboxSurface(String type) {
+        LetterboxSurface(@NonNull String type) {
             mType = type;
         }
 
@@ -394,28 +386,28 @@
             t.setLayer(mInputSurface, TASK_CHILD_LAYER_TASK_OVERLAY);
         }
 
-        void attachInput(WindowState win) {
-            if (mInputInterceptor != null) {
-                mInputInterceptor.dispose();
+        void attachInput(@NonNull WindowState windowState) {
+            if (mInputInterceptor != null || windowState.mDisplayContent == null) {
+                return;
             }
             // TODO(b/371179559): only detect double tap on LB surfaces not used for cutout area.
             // Potentially, the input interceptor may still be needed for slippery feature.
-            mInputInterceptor = new InputInterceptor("Letterbox_" + mType + "_", win);
+            mInputInterceptor = new InputInterceptor("Letterbox_" + mType + "_", windowState);
         }
 
-        public void remove() {
-            if (mSurface != null) {
-                mTransactionFactory.get().remove(mSurface).apply();
-                mSurface = null;
-            }
-            if (mInputSurface != null) {
-                mTransactionFactory.get().remove(mInputSurface).apply();
-                mInputSurface = null;
-            }
+        void remove(@NonNull SurfaceControl.Transaction t) {
             if (mInputInterceptor != null) {
                 mInputInterceptor.dispose();
                 mInputInterceptor = null;
             }
+            if (mSurface != null) {
+                t.remove(mSurface);
+            }
+            if (mInputSurface != null) {
+                t.remove(mInputSurface);
+            }
+            mInputSurface = null;
+            mSurface = null;
         }
 
         public int getWidth() {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3d28685..4f36476 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1941,7 +1941,8 @@
         if (Flags.enableTopVisibleRootTaskPerUserTracking()) {
             final IntArray visibleRootTasks = new IntArray();
             forAllRootTasks(rootTask -> {
-                if (mCurrentUser == rootTask.mUserId && rootTask.isVisibleRequested()) {
+                if ((mCurrentUser == rootTask.mUserId || rootTask.showForAllUsers())
+                        && rootTask.isVisible()) {
                     visibleRootTasks.add(rootTask.getRootTaskId());
                 }
             }, /* traverseTopToBottom */ false);
@@ -2045,6 +2046,11 @@
 
             if (Flags.enableTopVisibleRootTaskPerUserTracking()) {
                 final IntArray rootTasks = mUserVisibleRootTasks.get(userId, new IntArray());
+                // If root task already exists in the list, move it to the top instead.
+                final int rootTaskIndex = rootTasks.indexOf(rootTask.getRootTaskId());
+                if (rootTaskIndex != -1) {
+                    rootTasks.remove(rootTaskIndex);
+                }
                 rootTasks.add(rootTask.getRootTaskId());
                 mUserVisibleRootTasks.put(userId, rootTasks);
             } else {
@@ -2926,7 +2932,6 @@
     }
 
     void prepareForShutdown() {
-        mWindowManager.mSnapshotController.mTaskSnapshotController.prepareShutdown();
         for (int i = 0; i < getChildCount(); i++) {
             createSleepToken("shutdown", getChildAt(i).mDisplayId);
         }
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
index 38e0115..efc68aa 100644
--- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -95,8 +95,9 @@
         if (mediaProjectionInfo.getLaunchCookie() == null) {
             mRecordedWC = (WindowContainer) mWms.mRoot.getDefaultDisplay();
         } else {
-            mRecordedWC = mWms.mRoot.getActivity(activity -> activity.mLaunchCookie
-                    == mediaProjectionInfo.getLaunchCookie().binder).getTask();
+            final ActivityRecord matchingActivity = mWms.mRoot.getActivity(activity ->
+                    activity.mLaunchCookie == mediaProjectionInfo.getLaunchCookie().binder);
+            mRecordedWC = matchingActivity != null ? matchingActivity.getTask() : null;
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9062afb..d92301b 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3707,10 +3707,21 @@
 
                 // Boost the adjacent TaskFragment for dimmer if needed.
                 final TaskFragment taskFragment = wc.asTaskFragment();
-                if (taskFragment != null && taskFragment.isEmbedded()) {
-                    final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
-                    if (adjacentTf != null && adjacentTf.shouldBoostDimmer()) {
-                        adjacentTf.assignLayer(t, layer++);
+                if (taskFragment != null && taskFragment.isEmbedded()
+                        && taskFragment.hasAdjacentTaskFragment()) {
+                    if (Flags.allowMultipleAdjacentTaskFragments()) {
+                        final int[] nextLayer = { layer };
+                        taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
+                            if (adjacentTf.shouldBoostDimmer()) {
+                                adjacentTf.assignLayer(t, nextLayer[0]++);
+                            }
+                        });
+                        layer = nextLayer[0];
+                    } else {
+                        final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
+                        if (adjacentTf.shouldBoostDimmer()) {
+                            adjacentTf.assignLayer(t, layer++);
+                        }
                     }
                 }
 
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 9564c59..3d0b41b 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1045,7 +1045,7 @@
                                 + adjacentFlagRootTask);
             }
 
-            if (adjacentFlagRootTask.getAdjacentTaskFragment() == null) {
+            if (!adjacentFlagRootTask.hasAdjacentTaskFragment()) {
                 throw new UnsupportedOperationException(
                         "Can't set non-adjacent root as launch adjacent flag root tr="
                                 + adjacentFlagRootTask);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 38a2ebe..7d300e98 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -36,7 +36,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
+import com.android.window.flags.Flags;
 
+import java.util.ArrayList;
 import java.util.Set;
 
 /**
@@ -154,6 +156,8 @@
      * The attributes of task snapshot are based on task configuration. But sometimes the
      * configuration may have been changed during a transition, so supply the ChangeInfo that
      * stored the previous appearance of the closing task.
+     *
+     * The snapshot won't be created immediately if it should be captured as fake snapshot.
      */
     void recordSnapshot(Task task, Transition.ChangeInfo changeInfo) {
         mCurrentChangeInfo = changeInfo;
@@ -164,13 +168,35 @@
         }
     }
 
-    TaskSnapshot recordSnapshot(Task task) {
-        final TaskSnapshot snapshot = recordSnapshotInner(task);
-        if (snapshot != null && !task.isActivityTypeHome()) {
-            mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
-            task.onSnapshotChanged(snapshot);
+    void recordSnapshot(Task task) {
+        if (shouldDisableSnapshots()) {
+            return;
         }
-        return snapshot;
+        final SnapshotSupplier supplier = getRecordSnapshotSupplier(task);
+        if (supplier == null) {
+            return;
+        }
+        final int mode = getSnapshotMode(task);
+        if (Flags.excludeDrawingAppThemeSnapshotFromLock() && mode == SNAPSHOT_MODE_APP_THEME) {
+            mService.mH.post(supplier::handleSnapshot);
+        } else {
+            supplier.handleSnapshot();
+        }
+    }
+
+    /**
+     * Note that the snapshot is not created immediately, if the returned supplier is non-null, the
+     * caller must call {@link AbsAppSnapshotController.SnapshotSupplier#get} or
+     * {@link AbsAppSnapshotController.SnapshotSupplier#handleSnapshot} to complete the entire
+     * record request.
+     */
+    SnapshotSupplier getRecordSnapshotSupplier(Task task) {
+        return recordSnapshotInner(task, true /* allowAppTheme */, snapshot -> {
+            if (!task.isActivityTypeHome()) {
+                mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+                task.onSnapshotChanged(snapshot);
+            }
+        });
     }
 
     /**
@@ -328,27 +354,38 @@
      * Record task snapshots before shutdown.
      */
     void prepareShutdown() {
-        if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
+        if (!Flags.recordTaskSnapshotsBeforeShutdown()) {
             return;
         }
-        // Make write items run in a batch.
-        mPersister.mSnapshotPersistQueue.setPaused(true);
-        mPersister.mSnapshotPersistQueue.prepareShutdown();
-        for (int i = 0; i < mService.mRoot.getChildCount(); i++) {
-            mService.mRoot.getChildAt(i).forAllLeafTasks(task -> {
-                if (task.isVisible() && !task.isActivityTypeHome()) {
-                    final TaskSnapshot snapshot = captureSnapshot(task);
-                    if (snapshot != null) {
-                        mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+        final ArrayList<SnapshotSupplier> supplierArrayList = new ArrayList<>();
+        synchronized (mService.mGlobalLock) {
+            // Make write items run in a batch.
+            mPersister.mSnapshotPersistQueue.setPaused(true);
+            mPersister.mSnapshotPersistQueue.prepareShutdown();
+            for (int i = 0; i < mService.mRoot.getChildCount(); i++) {
+                mService.mRoot.getChildAt(i).forAllLeafTasks(task -> {
+                    if (task.isVisible() && !task.isActivityTypeHome()) {
+                        final SnapshotSupplier supplier = captureSnapshot(task,
+                                true /* allowAppTheme */);
+                        if (supplier != null) {
+                            supplier.setConsumer(t ->
+                                    mPersister.persistSnapshot(task.mTaskId, task.mUserId, t));
+                            supplierArrayList.add(supplier);
+                        }
                     }
-                }
-            }, true /* traverseTopToBottom */);
+                }, true /* traverseTopToBottom */);
+            }
         }
-        mPersister.mSnapshotPersistQueue.setPaused(false);
+        for (int i = supplierArrayList.size() - 1; i >= 0; --i) {
+            supplierArrayList.get(i).handleSnapshot();
+        }
+        synchronized (mService.mGlobalLock) {
+            mPersister.mSnapshotPersistQueue.setPaused(false);
+        }
     }
 
     void waitFlush(long timeout) {
-        if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
+        if (!Flags.recordTaskSnapshotsBeforeShutdown()) {
             return;
         }
         mPersister.mSnapshotPersistQueue.waitFlush(timeout);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1f539a1..a3d71db 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1589,7 +1589,7 @@
         cleanUpInternal();
 
         // Handle back animation if it's already started.
-        mController.mAtm.mBackNavigationController.onTransitionFinish(mTargets, this);
+        mController.mAtm.mBackNavigationController.onTransitionFinish(this);
         mController.mFinishingTransition = null;
         mController.mSnapshotController.onTransitionFinish(mType, mTargets);
         // Resume snapshot persist thread after snapshot controller analysis this transition.
@@ -2542,15 +2542,16 @@
             // TaskFragment doesn't contain occluded ActivityRecord.
             return true;
         }
-        final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
-        if (adjacentTaskFragment != null) {
-            // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be
-            // hidden unless any of them are translucent.
-            return adjacentTaskFragment.isTranslucentForTransition();
-        } else {
+        if (!taskFragment.hasAdjacentTaskFragment()) {
             // Non-filling without adjacent is considered as translucent.
             return !wc.fillsParent();
         }
+        // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be
+        // hidden unless any of them are translucent.
+        if (!Flags.allowMultipleAdjacentTaskFragments()) {
+            return taskFragment.getAdjacentTaskFragment().isTranslucentForTransition();
+        }
+        return taskFragment.forOtherAdjacentTaskFragments(TaskFragment::isTranslucentForTransition);
     }
 
     private void updatePriorVisibility() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index db62ceb..04650b9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -10084,14 +10084,16 @@
         TaskSnapshot taskSnapshot;
         final long token = Binder.clearCallingIdentity();
         try {
+            final Supplier<TaskSnapshot> supplier;
             synchronized (mGlobalLock) {
                 Task task = mRoot.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
                 if (task == null) {
                     throw new IllegalArgumentException(
                             "Failed to find matching task for taskId=" + taskId);
                 }
-                taskSnapshot = mTaskSnapshotController.captureSnapshot(task);
+                supplier = mTaskSnapshotController.captureSnapshot(task, true /* allowAppTheme */);
             }
+            taskSnapshot = supplier != null ? supplier.get() : null;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4078726..9ab9a8f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -3121,10 +3121,10 @@
         if (com.android.ranging.flags.Flags.rangingStackEnabled()) {
             if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)
                     || context.getPackageManager().hasSystemFeature(
-                            PackageManager.FEATURE_WIFI_RTT)
+                            PackageManager.FEATURE_WIFI_AWARE)
                     || (com.android.ranging.flags.Flags.rangingCsEnabled()
                             && context.getPackageManager().hasSystemFeature(
-                                    PackageManager.FEATURE_BLUETOOTH_LE_CHANNEL_SOUNDING))) {
+                                    PackageManager.FEATURE_BLUETOOTH_LE))) {
                 t.traceBegin("RangingService");
                 // TODO: b/375264320 - Remove after RELEASE_RANGING_STACK is ramped to next.
                 try {
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index d2c91ff..232bb83 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -286,14 +286,21 @@
                 return@forEach
             }
             var newFlags = oldFlags
+            val isSystemOrInstalled =
+                packageState.isSystem || packageState.getUserStateOrDefault(userId).isInstalled
             newFlags =
                 if (
-                    newFlags.hasBits(PermissionFlags.ROLE) ||
-                        newFlags.hasBits(PermissionFlags.PREGRANT)
+                    isSystemOrInstalled && (
+                        newFlags.hasBits(PermissionFlags.ROLE) ||
+                            newFlags.hasBits(PermissionFlags.PREGRANT)
+                    )
                 ) {
                     newFlags or PermissionFlags.RUNTIME_GRANTED
                 } else {
-                    newFlags andInv PermissionFlags.RUNTIME_GRANTED
+                    newFlags andInv (
+                        PermissionFlags.RUNTIME_GRANTED or PermissionFlags.ROLE or
+                            PermissionFlags.PREGRANT
+                    )
                 }
             newFlags = newFlags andInv USER_SETTABLE_MASK
             if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) {
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
index 1237095..8b357862d 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
@@ -72,7 +72,8 @@
         } else {
             mockPackageState(
                 APP_ID_1,
-                mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+                mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0)),
+                true
             )
         }
         setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
index d00e2c6..1f45792 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
@@ -33,6 +33,7 @@
 import android.content.Intent;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
+import android.os.BinderProxy;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -290,11 +291,15 @@
     }
 
     /**
-     * Return true if the freezer is enabled on this platform.
+     * Return true if the freezer is enabled on this platform and if freezer notifications are
+     * supported.  It is not enough to test that the freezer notification feature is enabled
+     * because some devices do not have the necessary kernel support.
      */
     private boolean isAppFreezerEnabled() {
         try {
-            return mActivityManager.getService().isAppFreezerEnabled();
+            return mActivityManager.getService().isAppFreezerEnabled()
+                    && android.os.Flags.binderFrozenStateChangeCallback()
+                    && BinderProxy.isFrozenStateChangeCallbackSupported();
         } catch (Exception e) {
             Log.e(TAG, "isAppFreezerEnabled() failed: " + e);
             return false;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index b7b4f04..a9ad435 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -23,12 +23,14 @@
 import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
 import static android.Manifest.permission.MANAGE_DISPLAYS;
 import static android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE;
+import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_SYSTEM;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
 import static android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
@@ -123,6 +125,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.test.FakePermissionEnforcer;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -444,8 +447,6 @@
         when(mContext.getResources()).thenReturn(mResources);
         mUserManager = Mockito.spy(mContext.getSystemService(UserManager.class));
 
-        mPermissionEnforcer.grant(CONTROL_DISPLAY_BRIGHTNESS);
-        mPermissionEnforcer.grant(MODIFY_USER_PREFERRED_DISPLAY_MODE);
         doReturn(Context.PERMISSION_ENFORCER_SERVICE).when(mContext).getSystemServiceName(
                 eq(PermissionEnforcer.class));
         doReturn(mPermissionEnforcer).when(mContext).getSystemService(
@@ -2576,11 +2577,11 @@
                 new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
                 new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 3));
         assertEquals(
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM),
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+                new HdrConversionMode(HDR_CONVERSION_SYSTEM),
+                new HdrConversionMode(HDR_CONVERSION_SYSTEM));
         assertNotEquals(
                 new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+                new HdrConversionMode(HDR_CONVERSION_SYSTEM));
     }
 
     @Test
@@ -2601,7 +2602,7 @@
                         + "HDR_CONVERSION_SYSTEM",
                 IllegalArgumentException.class,
                 () -> displayManager.setHdrConversionModeInternal(new HdrConversionMode(
-                        HdrConversionMode.HDR_CONVERSION_SYSTEM,
+                        HDR_CONVERSION_SYSTEM,
                         Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION)));
     }
 
@@ -2678,7 +2679,7 @@
         displayManager.setUserDisabledHdrTypesInternal(new int [0]);
         displayManager.setAreUserDisabledHdrTypesAllowedInternal(true);
         displayManager.setHdrConversionModeInternal(
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+                new HdrConversionMode(HDR_CONVERSION_SYSTEM));
 
         assertEquals(1, mAllowedHdrOutputTypes.length);
         assertTrue(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION == mAllowedHdrOutputTypes[0]);
@@ -2732,7 +2733,7 @@
         assertTrue(logicalDisplay.getDisplayInfoLocked().isForceSdr);
 
         displayManager.setHdrConversionModeInternal(
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+                new HdrConversionMode(HDR_CONVERSION_SYSTEM));
         assertFalse(logicalDisplay.getDisplayInfoLocked().isForceSdr);
     }
 
@@ -3360,6 +3361,7 @@
 
     @Test
     public void testOnUserSwitching_UpdatesBrightness() {
+        mPermissionEnforcer.grant(CONTROL_DISPLAY_BRIGHTNESS);
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
@@ -3411,6 +3413,7 @@
 
     @Test
     public void testOnUserSwitching_brightnessForNewUserIsDefault() {
+        mPermissionEnforcer.grant(CONTROL_DISPLAY_BRIGHTNESS);
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
@@ -3439,7 +3442,8 @@
     }
 
     @Test
-    public void testResolutionChangeGetsBackedUp_FeatureFlagFalse() throws Exception {
+    public void testResolutionChangeGetsBackedUp_FeatureFlagFalse() {
+        mPermissionEnforcer.grant(MODIFY_USER_PREFERRED_DISPLAY_MODE);
         when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(false);
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mBasicInjector);
@@ -3465,6 +3469,7 @@
 
     @Test
     public void testBrightnessUpdates() {
+        mPermissionEnforcer.grant(CONTROL_DISPLAY_BRIGHTNESS);
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
@@ -3533,6 +3538,7 @@
 
     @Test
     public void testResolutionChangeGetsBackedUp() throws Exception {
+        mPermissionEnforcer.grant(MODIFY_USER_PREFERRED_DISPLAY_MODE);
         when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(true);
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mBasicInjector);
@@ -3911,9 +3917,9 @@
         waitForIdleHandler(handler);
 
         // Create a default display device
-        createFakeDisplayDevice(displayManager, new float[] {60f}, Display.TYPE_INTERNAL);
+        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
         // Create a non-default display device
-        createFakeDisplayDevice(displayManager, new float[] {60f}, Display.TYPE_EXTERNAL);
+        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
 
         Settings.Secure.putInt(mContext.getContentResolver(), MIRROR_BUILT_IN_DISPLAY, 1);
         final ContentObserver observer = displayManager.getSettingsObserver();
@@ -3923,6 +3929,300 @@
         assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
     }
 
+    @Test
+    public void startWifiDisplayScan_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, displayManagerBinderService::startWifiDisplayScan);
+    }
+
+    @Test
+    public void stopWifiDisplayScan_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, displayManagerBinderService::stopWifiDisplayScan);
+    }
+
+    @Test
+    public void connectWifiDisplay_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class,
+                () -> displayManagerBinderService.connectWifiDisplay("someAddress"));
+    }
+
+    @Test
+    public void renameWifiDisplay_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class,
+                () -> displayManagerBinderService.renameWifiDisplay("someAddress", "someAlias"));
+    }
+
+    @Test
+    public void forgetWifiDisplay_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class,
+                () -> displayManagerBinderService.forgetWifiDisplay("someAddress"));
+    }
+
+    @Test
+    public void pauseWifiDisplay_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, displayManagerBinderService::pauseWifiDisplay);
+    }
+
+    @Test
+    public void resumeWifiDisplay_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, displayManagerBinderService::resumeWifiDisplay);
+    }
+
+    @Test
+    public void setUserDisabledHdrTypes_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () ->
+                displayManagerBinderService.setUserDisabledHdrTypes(new int[0]));
+    }
+
+    @Test
+    public void setAreUserDisabledHdrTypesAllowed_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () ->
+                displayManagerBinderService.setAreUserDisabledHdrTypesAllowed(true));
+    }
+
+    @Test
+    public void requestColorMode_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () -> displayManagerBinderService.requestColorMode(
+                Display.DEFAULT_DISPLAY, Display.COLOR_MODE_DEFAULT));
+    }
+
+    @Test
+    public void getBrightnessEvents_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () ->
+                displayManagerBinderService.getBrightnessEvents("somePackage"));
+    }
+
+    @Test
+    public void getAmbientBrightnessStats_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class,
+                displayManagerBinderService::getAmbientBrightnessStats);
+    }
+
+    @Test
+    public void setBrightnessConfigurationForUser_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () ->
+                displayManagerBinderService.setBrightnessConfigurationForUser(
+                        new BrightnessConfiguration.Builder(/* lux= */ new float[]{0, 100},
+                                /* nits= */ new float[]{100, 200}).build(), UserHandle.USER_SYSTEM,
+                        "somePackage"));
+    }
+
+    @Test
+    public void setBrightnessConfigurationForDisplay_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () ->
+                displayManagerBinderService.setBrightnessConfigurationForDisplay(
+                        new BrightnessConfiguration.Builder(/* lux= */ new float[]{0, 100},
+                                /* nits= */ new float[]{100, 200}).build(), "uniqueId",
+                        UserHandle.USER_SYSTEM, "somePackage"));
+    }
+
+    @Test
+    public void getBrightnessConfigurationForDisplay_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () ->
+                displayManagerBinderService.getBrightnessConfigurationForDisplay("uniqueId",
+                        UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void getDefaultBrightnessConfiguration_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class,
+                displayManagerBinderService::getDefaultBrightnessConfiguration);
+    }
+
+    @Test
+    public void getBrightnessInfo_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () ->
+                displayManagerBinderService.getBrightnessInfo(Display.DEFAULT_DISPLAY));
+    }
+
+    @Test
+    public void setTemporaryBrightness_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () ->
+                displayManagerBinderService.setTemporaryBrightness(Display.DEFAULT_DISPLAY, 0.3f));
+    }
+
+    @Test
+    public void setBrightness_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () ->
+                displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, 0.3f));
+    }
+
+    @Test
+    public void getBrightness_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () ->
+                displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY));
+    }
+
+    @Test
+    public void setTemporaryAutoBrightnessAdjustment_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () ->
+                displayManagerBinderService.setTemporaryAutoBrightnessAdjustment(0.1f));
+    }
+
+    @Test
+    public void setUserPreferredDisplayMode_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () -> displayManagerBinderService
+                .setUserPreferredDisplayMode(Display.DEFAULT_DISPLAY, new Display.Mode(
+                        /* width= */ 800, /* height= */ 600, /* refreshRate= */ 60)));
+    }
+
+    @Test
+    public void setHdrConversionMode_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () -> displayManagerBinderService
+                .setHdrConversionMode(new HdrConversionMode(HDR_CONVERSION_SYSTEM)));
+    }
+
+    @Test
+    public void setShouldAlwaysRespectAppRequestedMode_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () -> displayManagerBinderService
+                .setShouldAlwaysRespectAppRequestedMode(true));
+    }
+
+    @Test
+    public void shouldAlwaysRespectAppRequestedMode_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class,
+                displayManagerBinderService::shouldAlwaysRespectAppRequestedMode);
+    }
+
+    @Test
+    public void setRefreshRateSwitchingType_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () -> displayManagerBinderService
+                .setRefreshRateSwitchingType(SWITCHING_TYPE_NONE));
+    }
+
+    @Test
+    public void requestDisplayModes_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () -> displayManagerBinderService
+                .requestDisplayModes(new Binder(), Display.DEFAULT_DISPLAY, new int[0]));
+    }
+
+    @Test
+    public void getDozeBrightnessSensorValueToBrightness_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () -> displayManagerBinderService
+                .getDozeBrightnessSensorValueToBrightness(Display.DEFAULT_DISPLAY));
+    }
+
+    @Test
+    public void getDefaultDozeBrightness_withoutPermission_shouldThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        assertThrows(SecurityException.class, () -> displayManagerBinderService
+                .getDefaultDozeBrightness(Display.DEFAULT_DISPLAY));
+    }
+
     private void initDisplayPowerController(DisplayManagerInternal localService) {
         localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
             @Override
diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 35f421e..de6f9bd 100644
--- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -78,12 +78,15 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.power.hint.HintManagerService.AppHintSession;
 import com.android.server.power.hint.HintManagerService.Injector;
 import com.android.server.power.hint.HintManagerService.NativeWrapper;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -93,6 +96,8 @@
 import org.mockito.stubbing.Answer;
 
 import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -111,6 +116,7 @@
  */
 public class HintManagerServiceTest {
     private static final String TAG = "HintManagerServiceTest";
+    private List<File> mFilesCreated = new ArrayList<>();
 
     private static WorkDuration makeWorkDuration(
             long timestamp, long duration, long workPeriodStartTime,
@@ -192,9 +198,9 @@
         mSupportInfo.sessionTags = -1;
         mSupportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
         mSupportInfo.headroom.isCpuSupported = true;
-        mSupportInfo.headroom.cpuMinIntervalMillis = 2000;
+        mSupportInfo.headroom.cpuMinIntervalMillis = 1000;
         mSupportInfo.headroom.isGpuSupported = true;
-        mSupportInfo.headroom.gpuMinIntervalMillis = 2000;
+        mSupportInfo.headroom.gpuMinIntervalMillis = 1000;
         mSupportInfo.compositionData = new SupportInfo.CompositionDataSupportInfo();
         return mSupportInfo;
     }
@@ -243,6 +249,13 @@
         LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
     }
 
+    @After
+    public void tearDown() {
+        for (File file : mFilesCreated) {
+            file.delete();
+        }
+    }
+
     /**
      * Mocks the creation calls, but without support for new createHintSessionWithConfig method
      */
@@ -1327,6 +1340,58 @@
         });
     }
 
+    @Test
+    public void testCpuHeadroomCpuProcStatPath() throws Exception {
+        File dir = InstrumentationRegistry.getTargetContext().getFilesDir();
+        dir.mkdir();
+        String procStatFileStr = "mock_proc_stat";
+        File file = new File(dir, procStatFileStr);
+        mFilesCreated.add(file);
+        try (FileOutputStream output = new FileOutputStream(file)) {
+            output.write("cpu  2000 3000 4000 0 0 0 0 0 0 0".getBytes());
+        }
+        HintManagerService service = createService();
+        service.setProcStatPathOverride(file.getPath());
+
+        CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal();
+        CpuHeadroomParams halParams1 = new CpuHeadroomParams();
+        halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN;
+        halParams1.tids = new int[]{Process.myPid()};
+
+        float headroom1 = 0.1f;
+        CpuHeadroomResult halRet1 = CpuHeadroomResult.globalHeadroom(headroom1);
+        when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(halRet1);
+        clearInvocations(mIPowerMock);
+        assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
+        // expire the cache but cpu proc hasn't changed so we expect no value return
+        Thread.sleep(1100);
+        clearInvocations(mIPowerMock);
+        assertEquals(null, service.getBinderServiceInstance().getCpuHeadroom(params1));
+        verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
+
+        // update user jiffies with 500 equivalent jiffies, which is not sufficient cpu time
+        Thread.sleep(1100);
+        try (FileOutputStream output = new FileOutputStream(file)) {
+            output.write(("cpu  " + (2000 + (int) (500 / service.mJiffyMillis))
+                    + " 3000 4000 0 0 0 0 0 0 0").getBytes());
+        }
+        clearInvocations(mIPowerMock);
+        assertEquals(null, service.getBinderServiceInstance().getCpuHeadroom(params1));
+        verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
+
+        // update nice jiffies with 600 equivalent jiffies, now it exceeds 1000ms requirement
+        Thread.sleep(1100);
+        try (FileOutputStream output = new FileOutputStream(file)) {
+            output.write(("cpu  " + (2000 + (int) (500 / service.mJiffyMillis))
+                    + " " + +(3000 + (int) (600 / service.mJiffyMillis))
+                    + " 4000 0 0 0 0 0 0 0").getBytes());
+        }
+        clearInvocations(mIPowerMock);
+        assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
+    }
+
 
     @Test
     @EnableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK})
@@ -1397,8 +1462,8 @@
         verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
 
-        // after 1 more second it should be served with cache still
-        Thread.sleep(1000);
+        // after 500ms more it should be served with cache
+        Thread.sleep(500);
         clearInvocations(mIPowerMock);
         assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
         assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
@@ -1410,8 +1475,8 @@
         verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
 
-        // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
-        Thread.sleep(1100);
+        // after 1+ seconds it should be served from HAL as it exceeds 1000 millis interval
+        Thread.sleep(600);
         clearInvocations(mIPowerMock);
         assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
         assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
@@ -1519,8 +1584,8 @@
         verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
         verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
 
-        // after 1 more second it should be served with cache still
-        Thread.sleep(1000);
+        // after 500ms it should be served with cache
+        Thread.sleep(500);
         clearInvocations(mIPowerMock);
         assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
         assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
@@ -1528,8 +1593,8 @@
         verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
         verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
 
-        // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
-        Thread.sleep(1100);
+        // after 1+ seconds it should be served from HAL as it exceeds 1000 millis interval
+        Thread.sleep(600);
         clearInvocations(mIPowerMock);
         assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
         assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index d8a9400..69feb1d 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -6,5 +6,6 @@
 per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
 per-file BatteryServiceTest.java = file:platform/hardware/interfaces:/health/OWNERS
 per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
+per-file GestureLauncherServiceTest.java = file:/INPUT_OWNERS
 per-file PinnerServiceTest.java = file:/apct-tests/perftests/OWNERS
 per-file SecurityStateTest.java = file:/SECURITY_STATE_OWNERS
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 b1df0f1..c7a06b8 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
@@ -31,6 +31,7 @@
 import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.security.advancedprotection.AdvancedProtectionManager;
 import android.security.advancedprotection.IAdvancedProtectionCallback;
 
 import androidx.annotation.NonNull;
@@ -54,7 +55,8 @@
     private Context mContext;
     private AdvancedProtectionService.AdvancedProtectionStore mStore;
     private TestLooper mLooper;
-    AdvancedProtectionFeature mFeature = new AdvancedProtectionFeature("test-id");
+    AdvancedProtectionFeature mTestFeature2g = new AdvancedProtectionFeature(
+            AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G);
 
     @Before
     public void setup() throws Settings.SettingNotFoundException {
@@ -105,7 +107,7 @@
                     @NonNull
                     @Override
                     public AdvancedProtectionFeature getFeature() {
-                        return mFeature;
+                        return mTestFeature2g;
                     }
 
                     @Override
@@ -135,7 +137,7 @@
                     @NonNull
                     @Override
                     public AdvancedProtectionFeature getFeature() {
-                        return mFeature;
+                        return mTestFeature2g;
                     }
 
                     @Override
@@ -165,7 +167,7 @@
                     @NonNull
                     @Override
                     public AdvancedProtectionFeature getFeature() {
-                        return mFeature;
+                        return mTestFeature2g;
                     }
 
                     @Override
@@ -238,8 +240,10 @@
 
     @Test
     public void testGetFeatures() {
-        AdvancedProtectionFeature feature1 = new AdvancedProtectionFeature("id-1");
-        AdvancedProtectionFeature feature2 = new AdvancedProtectionFeature("id-2");
+        AdvancedProtectionFeature feature1 = new AdvancedProtectionFeature(
+                AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G);
+        AdvancedProtectionFeature feature2 = new AdvancedProtectionFeature(
+                AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES);
         AdvancedProtectionHook hook = new AdvancedProtectionHook(mContext, true) {
             @NonNull
             @Override
@@ -268,8 +272,10 @@
 
     @Test
     public void testGetFeatures_featureNotAvailable() {
-        AdvancedProtectionFeature feature1 = new AdvancedProtectionFeature("id-1");
-        AdvancedProtectionFeature feature2 = new AdvancedProtectionFeature("id-2");
+        AdvancedProtectionFeature feature1 = new AdvancedProtectionFeature(
+                AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G);
+        AdvancedProtectionFeature feature2 = new AdvancedProtectionFeature(
+                AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES);
         AdvancedProtectionHook hook = new AdvancedProtectionHook(mContext, true) {
             @NonNull
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
index a7fc10f..948371f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
@@ -29,6 +29,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.never;
 
@@ -253,7 +254,11 @@
      */
     @Test
     public void testSkipRecordActivity() {
-        doReturn(createSnapshot()).when(mActivitySnapshotController).recordSnapshotInner(any());
+        final AbsAppSnapshotController.SnapshotSupplier supplier =
+                new AbsAppSnapshotController.SnapshotSupplier();
+        supplier.setSupplier(this::createSnapshot);
+        doReturn(supplier).when(mActivitySnapshotController).recordSnapshotInner(
+                any(), anyBoolean(), any());
         final Task task = createTask(mDisplayContent);
 
         mSnapshotPersistQueue.setPaused(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
index 8747cfa..9d191ce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -335,8 +335,7 @@
         }
 
         private AppCompatOrientationOverrides getTopOrientationOverrides() {
-            return activity().top().mAppCompatController.getAppCompatOverrides()
-                    .getAppCompatOrientationOverrides();
+            return activity().top().mAppCompatController.getAppCompatOrientationOverrides();
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index 90bf5f0..a21ab5d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -601,8 +601,7 @@
         }
 
         private AppCompatOrientationOverrides getTopOrientationOverrides() {
-            return activity().top().mAppCompatController.getAppCompatOverrides()
-                    .getAppCompatOrientationOverrides();
+            return activity().top().mAppCompatController.getAppCompatOrientationOverrides();
         }
 
         private AppCompatOrientationPolicy getTopAppCompatOrientationPolicy() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
index 1edbcd5..463254c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
@@ -23,14 +23,10 @@
 
 import android.compat.testing.PlatformCompatChangeRule;
 import android.graphics.Rect;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.annotation.NonNull;
 
-import com.android.window.flags.Flags;
-
 import junit.framework.Assert;
 
 import org.junit.Rule;
@@ -125,8 +121,7 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
-    public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
+    public void testAllowReachabilityForThinLetterbox_disableForThinLetterboxing() {
         runTestScenario((robot) -> {
             robot.activity().createActivityWithComponent();
 
@@ -142,24 +137,6 @@
         });
     }
 
-    @Test
-    @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
-    public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
-        runTestScenario((robot) -> {
-            robot.activity().createActivityWithComponent();
-
-            robot.configureIsVerticalThinLetterboxed(/* isThin */ true);
-            robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
-            robot.configureIsHorizontalThinLetterboxed(/* isThin */ true);
-            robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
-
-            robot.configureIsVerticalThinLetterboxed(/* isThin */ false);
-            robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
-            robot.configureIsHorizontalThinLetterboxed(/* isThin */ false);
-            robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
-        });
-    }
-
     /**
      * Runs a test scenario providing a Robot.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 429a396a..de4b6fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -150,8 +150,8 @@
                 mProcess).build();
 
         // Use a new TestIWindow so we don't collect events for other windows
-        final WindowState window = createWindow(null, TYPE_BASE_APPLICATION, activity, name,
-                ownerId, false, new TestIWindow());
+        final WindowState window = newWindowBuilder(name, TYPE_BASE_APPLICATION).setWindowToken(
+                activity).setOwnerId(ownerId).setClientWindow(new TestIWindow()).build();
         InputChannel channel = new InputChannel();
         window.openInputChannel(channel);
         window.mHasSurface = true;
@@ -249,7 +249,7 @@
                         mTarget.mDeferDragStateClosed = true;
                         mTarget.reportDropWindow(mWindow.mInputChannelToken, 0, 0);
                         // Verify the drop event includes the drag surface
-                        mTarget.handleMotionEvent(false, 0, 0);
+                        mTarget.handleMotionEvent(false, mWindow.getDisplayId(), 0, 0);
                         final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1);
                         assertTrue(dropEvent.getDragSurface() != null);
 
@@ -296,7 +296,7 @@
                             0).getClipData().willParcelWithActivityInfo());
 
                     mTarget.reportDropWindow(globalInterceptWindow.mInputChannelToken, 0, 0);
-                    mTarget.handleMotionEvent(false, 0, 0);
+                    mTarget.handleMotionEvent(false, globalInterceptWindow.getDisplayId(), 0, 0);
                     mToken = globalInterceptWindow.mClient.asBinder();
 
                     // Verify the drop event is only sent for the global intercept window
@@ -334,8 +334,8 @@
                     try {
                         mTarget.mDeferDragStateClosed = true;
                         mTarget.reportDropWindow(mWindow.mInputChannelToken, 0, 0);
-                        // // Verify the drop event does not have the drag flags
-                        mTarget.handleMotionEvent(false, 0, 0);
+                        // Verify the drop event does not have the drag flags
+                        mTarget.handleMotionEvent(false, mWindow.getDisplayId(), 0, 0);
                         final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1);
                         assertTrue(dropEvent.getDragFlags() == (View.DRAG_FLAG_GLOBAL
                                 | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG));
@@ -520,7 +520,7 @@
 
                     // Verify after consuming that the drag surface is relinquished
                     mTarget.reportDropWindow(otherWindow.mInputChannelToken, 0, 0);
-                    mTarget.handleMotionEvent(false, 0, 0);
+                    mTarget.handleMotionEvent(false, otherWindow.getDisplayId(), 0, 0);
                     mToken = otherWindow.mClient.asBinder();
                     mTarget.reportDropResult(otherIWindow, true);
 
@@ -551,7 +551,7 @@
 
                     // Verify after consuming that the drag surface is relinquished
                     mTarget.reportDropWindow(otherWindow.mInputChannelToken, 0, 0);
-                    mTarget.handleMotionEvent(false, 0, 0);
+                    mTarget.handleMotionEvent(false, otherWindow.getDisplayId(), 0, 0);
                     mToken = otherWindow.mClient.asBinder();
                     mTarget.reportDropResult(otherIWindow, false);
 
@@ -586,7 +586,8 @@
                 ClipData.newPlainText("label", "Test"), () -> {
                     // Trigger an unhandled drop and verify the global drag listener was called
                     mTarget.reportDropWindow(mWindow.mInputChannelToken, invalidXY, invalidXY);
-                    mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+                    mTarget.handleMotionEvent(false /* keepHandling */, mWindow.getDisplayId(),
+                            invalidXY, invalidXY);
                     mTarget.reportDropResult(mWindow.mClient, false);
                     mTarget.onUnhandledDropCallback(true);
                     mToken = null;
@@ -610,7 +611,8 @@
                 ClipData.newPlainText("label", "Test"), () -> {
                     // Trigger an unhandled drop and verify the global drag listener was called
                     mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
-                    mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+                    mTarget.handleMotionEvent(false /* keepHandling */, mWindow.getDisplayId(),
+                            invalidXY, invalidXY);
                     mTarget.onUnhandledDropCallback(true);
                     mToken = null;
                     try {
@@ -632,7 +634,8 @@
         startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
             // Trigger an unhandled drop and verify the global drag listener was not called
             mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
-            mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+            mTarget.handleMotionEvent(false /* keepHandling */, mDisplayContent.getDisplayId(),
+                    invalidXY, invalidXY);
             mToken = null;
             try {
                 verify(listener, never()).onUnhandledDrop(any(), any());
@@ -654,7 +657,8 @@
                 ClipData.newPlainText("label", "Test"), () -> {
                     // Trigger an unhandled drop and verify the global drag listener was called
                     mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
-                    mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+                    mTarget.handleMotionEvent(false /* keepHandling */,
+                            mDisplayContent.getDisplayId(), invalidXY, invalidXY);
 
                     // Verify that the unhandled drop listener callback timeout has been scheduled
                     final Handler handler = mTarget.getHandler();
@@ -673,7 +677,8 @@
     private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
         startDrag(flags, data, () -> {
             mTarget.reportDropWindow(mWindow.mInputChannelToken, dropX, dropY);
-            mTarget.handleMotionEvent(false /* keepHandling */, dropX, dropY);
+            mTarget.handleMotionEvent(false /* keepHandling */, mWindow.getDisplayId(), dropX,
+                    dropY);
             mToken = mWindow.mClient.asBinder();
         });
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java
index 7e1de47..51e0240 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java
@@ -62,6 +62,8 @@
     private Letterbox mLetterbox;
     private LetterboxTest.SurfaceControlMocker mSurfaces;
 
+    private WindowState mWindowState;
+
     @Before
     public void setUp() throws Exception {
         mSurfaces = new LetterboxTest.SurfaceControlMocker();
@@ -72,6 +74,7 @@
         doReturn(false).when(letterboxOverrides).hasWallpaperBackgroundForLetterbox();
         doReturn(0).when(letterboxOverrides).getLetterboxWallpaperBlurRadiusPx();
         doReturn(0.5f).when(letterboxOverrides).getLetterboxWallpaperDarkScrimAlpha();
+        mWindowState = createWindowState();
         mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
                 mock(AppCompatReachabilityPolicy.class), letterboxOverrides,
                 () -> mock(SurfaceControl.class));
@@ -83,7 +86,6 @@
     public void testSurface_createdHasSlipperyInput_scrollingFromLetterboxDisabled() {
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
 
-        attachInput();
         applySurfaceChanges();
 
         assertNotNull(mSurfaces.top);
@@ -100,7 +102,6 @@
     public void testInputSurface_notCreated_scrollingFromLetterboxDisabled() {
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
 
-        attachInput();
         applySurfaceChanges();
 
         assertNull(mSurfaces.topInput);
@@ -111,7 +112,6 @@
     public void testSurface_createdHasNoInput_scrollingFromLetterboxEnabled() {
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
 
-        attachInput();
         applySurfaceChanges();
 
         assertNotNull(mSurfaces.top);
@@ -124,7 +124,6 @@
     public void testInputSurface_createdHasSpyInput_scrollingFromLetterboxEnabled() {
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
 
-        attachInput();
         applySurfaceChanges();
 
         assertNotNull(mSurfaces.topInput);
@@ -141,7 +140,6 @@
     public void testInputSurfaceOrigin_applied_scrollingFromLetterboxEnabled() {
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
 
-        attachInput();
         applySurfaceChanges();
 
         verify(mTransaction).setPosition(mSurfaces.topInput, -1000, -2000);
@@ -152,7 +150,6 @@
     public void testInputSurfaceOrigin_changeCausesReapply_scrollingFromLetterboxEnabled() {
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
 
-        attachInput();
         applySurfaceChanges();
         clearInvocations(mTransaction);
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0));
@@ -166,13 +163,12 @@
 
     private void applySurfaceChanges() {
         mLetterbox.applySurfaceChanges(/* syncTransaction */ mTransaction,
-                /* pendingTransaction */ mTransaction);
+                /* pendingTransaction */ mTransaction, mWindowState);
     }
 
-    private void attachInput() {
+    private WindowState createWindowState() {
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
         final WindowToken windowToken = createTestWindowToken(0, mDisplayContent);
-        WindowState windowState = createWindowState(attrs, windowToken);
-        mLetterbox.attachInput(windowState);
+        return createWindowState(attrs, windowToken);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index 0baa517..a51a44f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -70,6 +70,7 @@
 
     private SurfaceControl mParentSurface = mock(SurfaceControl.class);
     private AppCompatLetterboxOverrides mLetterboxOverrides;
+    private WindowState mWindowState;
 
     @Before
     public void setUp() throws Exception {
@@ -81,6 +82,7 @@
         doReturn(false).when(mLetterboxOverrides).hasWallpaperBackgroundForLetterbox();
         doReturn(0).when(mLetterboxOverrides).getLetterboxWallpaperBlurRadiusPx();
         doReturn(0.5f).when(mLetterboxOverrides).getLetterboxWallpaperDarkScrimAlpha();
+        mWindowState = mock(WindowState.class);
         mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
                 mock(AppCompatReachabilityPolicy.class), mLetterboxOverrides, () -> mParentSurface);
         mTransaction = spy(StubTransaction.class);
@@ -320,7 +322,7 @@
 
     private void applySurfaceChanges() {
         mLetterbox.applySurfaceChanges(/* syncTransaction */ mTransaction,
-                /* pendingTransaction */ mTransaction);
+                /* pendingTransaction */ mTransaction, mWindowState);
     }
 
     static class SurfaceControlMocker implements Supplier<SurfaceControl.Builder> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 699ed02..7e62b89 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -1348,13 +1348,19 @@
                 WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         doReturn(rootTask3).when(mRootWindowContainer).getTopDisplayFocusedRootTask();
 
-        // Set up user ids and visibility
+        // Set up child tasks inside root tasks and set some of them visible
+        final Task task1 = new TaskBuilder(mSupervisor).setOnTop(true).setParentTask(
+                rootTask1).build();
+        final Task task2 = new TaskBuilder(mSupervisor).setOnTop(true).setParentTask(
+                rootTask2).build();
+        final Task task3 = new TaskBuilder(mSupervisor).setOnTop(true).setParentTask(
+                rootTask3).build();
         rootTask1.mUserId = mRootWindowContainer.mCurrentUser;
         rootTask2.mUserId = mRootWindowContainer.mCurrentUser;
         rootTask3.mUserId = mRootWindowContainer.mCurrentUser;
-        rootTask1.mVisibleRequested = false;
-        rootTask2.mVisibleRequested = true;
-        rootTask3.mVisibleRequested = true;
+        doReturn(false).when(task1).isVisible();
+        doReturn(true).when(task2).isVisible();
+        doReturn(true).when(task3).isVisible();
 
         // Switch to a different user
         int currentUser = mRootWindowContainer.mCurrentUser;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 201ff51..6a738ae5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -391,7 +391,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING)
     @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
     public void testRepositionLandscapeImmersiveAppWithDisplayCutout() {
         final int dw = 2100;
@@ -3783,7 +3782,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING)
     public void testImmersiveLetterboxAlignedToBottom_OverlappingNavbar() {
         assertLandscapeActivityAlignedToBottomWithNavbar(true /* immersive */);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 0cd036f..19c1ce2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -741,16 +741,16 @@
 
         // Not allowed because TaskFragments are not organized by the caller organizer.
         assertApplyTransactionDisallowed(mTransaction);
-        assertNull(mTaskFragment.getAdjacentTaskFragment());
-        assertNull(taskFragment2.getAdjacentTaskFragment());
+        assertFalse(mTaskFragment.hasAdjacentTaskFragment());
+        assertFalse(taskFragment2.hasAdjacentTaskFragment());
 
         mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
                 "Test:TaskFragmentOrganizer" /* processName */);
 
         // Not allowed because TaskFragment2 is not organized by the caller organizer.
         assertApplyTransactionDisallowed(mTransaction);
-        assertNull(mTaskFragment.getAdjacentTaskFragment());
-        assertNull(taskFragment2.getAdjacentTaskFragment());
+        assertFalse(mTaskFragment.hasAdjacentTaskFragment());
+        assertFalse(taskFragment2.hasAdjacentTaskFragment());
 
         mTaskFragment.onTaskFragmentOrganizerRemoved();
         taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
@@ -758,14 +758,14 @@
 
         // Not allowed because mTaskFragment is not organized by the caller organizer.
         assertApplyTransactionDisallowed(mTransaction);
-        assertNull(mTaskFragment.getAdjacentTaskFragment());
-        assertNull(taskFragment2.getAdjacentTaskFragment());
+        assertFalse(mTaskFragment.hasAdjacentTaskFragment());
+        assertFalse(taskFragment2.hasAdjacentTaskFragment());
 
         mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
                 "Test:TaskFragmentOrganizer" /* processName */);
 
         assertApplyTransactionAllowed(mTransaction);
-        assertEquals(taskFragment2, mTaskFragment.getAdjacentTaskFragment());
+        assertTrue(mTaskFragment.isAdjacentTo(taskFragment2));
     }
 
     @Test
@@ -790,14 +790,14 @@
 
         // Not allowed because TaskFragment is not organized by the caller organizer.
         assertApplyTransactionDisallowed(mTransaction);
-        assertEquals(taskFragment2, mTaskFragment.getAdjacentTaskFragment());
+        assertTrue(mTaskFragment.isAdjacentTo(taskFragment2));
 
         mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
                 "Test:TaskFragmentOrganizer" /* processName */);
 
         assertApplyTransactionAllowed(mTransaction);
-        assertNull(mTaskFragment.getAdjacentTaskFragment());
-        assertNull(taskFragment2.getAdjacentTaskFragment());
+        assertFalse(mTaskFragment.hasAdjacentTaskFragment());
+        assertFalse(taskFragment2.hasAdjacentTaskFragment());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index dafa96f..35a2546 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -365,7 +365,7 @@
 
         assertEquals(taskFragmentBounds, activity.getBounds());
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());
-        assertEquals(taskFragment1, taskFragment0.getAdjacentTaskFragment());
+        assertTrue(taskFragment0.isAdjacentTo(taskFragment1));
         assertEquals(taskFragment1, taskFragment0.getCompanionTaskFragment());
         assertNotEquals(TaskFragmentAnimationParams.DEFAULT, taskFragment0.getAnimationParams());
 
@@ -381,7 +381,7 @@
         assertEquals(taskBounds, taskFragment0.getBounds());
         assertEquals(taskBounds, activity.getBounds());
         assertEquals(Configuration.EMPTY, taskFragment0.getRequestedOverrideConfiguration());
-        assertNull(taskFragment0.getAdjacentTaskFragment());
+        assertFalse(taskFragment0.hasAdjacentTaskFragment());
         assertNull(taskFragment0.getCompanionTaskFragment());
         assertEquals(TaskFragmentAnimationParams.DEFAULT, taskFragment0.getAnimationParams());
         // Because the whole Task is entering PiP, no need to record for future reparent.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 6655932..c6b2a6b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -33,6 +33,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -45,12 +46,15 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArraySet;
 import android.window.TaskSnapshot;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.window.flags.Flags;
+
 import com.google.android.collect.Sets;
 
 import org.junit.Test;
@@ -285,4 +289,27 @@
 
         assertFalse(success);
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_EXCLUDE_DRAWING_APP_THEME_SNAPSHOT_FROM_LOCK)
+    public void testRecordTaskSnapshot() {
+        spyOn(mWm.mTaskSnapshotController.mCache);
+        spyOn(mWm.mTaskSnapshotController);
+        doReturn(false).when(mWm.mTaskSnapshotController).shouldDisableSnapshots();
+
+        final WindowState normalWindow = createWindow(null,
+                FIRST_APPLICATION_WINDOW, mDisplayContent, "normalWindow");
+        final TaskSnapshot snapshot = new TaskSnapshotPersisterTestBase.TaskSnapshotBuilder()
+                .setTopActivityComponent(normalWindow.mActivityRecord.mActivityComponent).build();
+        doReturn(snapshot).when(mWm.mTaskSnapshotController).snapshot(any());
+        final Task task = normalWindow.mActivityRecord.getTask();
+        mWm.mTaskSnapshotController.recordSnapshot(task);
+        verify(mWm.mTaskSnapshotController.mCache).putSnapshot(eq(task), any());
+        clearInvocations(mWm.mTaskSnapshotController.mCache);
+
+        normalWindow.mAttrs.flags |= FLAG_SECURE;
+        mWm.mTaskSnapshotController.recordSnapshot(task);
+        waitHandlerIdle(mWm.mH);
+        verify(mWm.mTaskSnapshotController.mCache).putSnapshot(eq(task), any());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index da4c522..1281be51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -909,8 +909,8 @@
         WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.setAdjacentRoots(info1.token, info2.token);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
-        assertEquals(task1.getAdjacentTaskFragment(), task2);
-        assertEquals(task2.getAdjacentTaskFragment(), task1);
+        assertTrue(task1.isAdjacentTo(task2));
+        assertTrue(task2.isAdjacentTo(task1));
 
         wct = new WindowContainerTransaction();
         wct.setLaunchAdjacentFlagRoot(info1.token);
@@ -921,8 +921,8 @@
         wct.clearAdjacentRoots(info1.token);
         wct.clearLaunchAdjacentFlagRoot(info1.token);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
-        assertEquals(task1.getAdjacentTaskFragment(), null);
-        assertEquals(task2.getAdjacentTaskFragment(), null);
+        assertFalse(task1.hasAdjacentTaskFragment());
+        assertFalse(task2.hasAdjacentTaskFragment());
         assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null);
     }
 
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index 49616c3..8ac3433 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -765,14 +765,14 @@
         } catch (PackageManager.NameNotFoundException e) {
             throw new RuntimeException(e);
         }
-        watchdog.registerHealthObserver(rollbackObserver, mTestExecutor);
+        watchdog.registerHealthObserver(mTestExecutor, rollbackObserver);
         return rollbackObserver;
     }
     RescuePartyObserver setUpRescuePartyObserver(PackageWatchdog watchdog) {
         setCrashRecoveryPropRescueBootCount(0);
         RescuePartyObserver rescuePartyObserver = spy(RescuePartyObserver.getInstance(mSpyContext));
         assertFalse(RescueParty.isRebootPropertySet());
-        watchdog.registerHealthObserver(rescuePartyObserver, mTestExecutor);
+        watchdog.registerHealthObserver(mTestExecutor, rescuePartyObserver);
         return rescuePartyObserver;
     }
 
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 928e232..1c50cb1 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -54,7 +54,6 @@
 import android.provider.DeviceConfig;
 import android.util.AtomicFile;
 import android.util.LongArrayQueue;
-import android.util.Slog;
 import android.util.Xml;
 
 import androidx.test.InstrumentationRegistry;
@@ -231,8 +230,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -248,10 +247,10 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog.registerHealthObserver(observer1, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.registerHealthObserver(observer2, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer1);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer1);
+        watchdog.registerHealthObserver(mTestExecutor, observer2);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B), SHORT_DURATION, observer2);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
                         new VersionedPackage(APP_B, VERSION_CODE)),
@@ -268,8 +267,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer);
         watchdog.unregisterHealthObserver(observer);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -285,10 +284,10 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog.registerHealthObserver(observer1, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.registerHealthObserver(observer2, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer1);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer1);
+        watchdog.registerHealthObserver(mTestExecutor, observer2);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer2);
         watchdog.unregisterHealthObserver(observer2);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -305,8 +304,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer);
         moveTimeForwardAndDispatch(SHORT_DURATION);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -322,10 +321,10 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog.registerHealthObserver(observer1, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.registerHealthObserver(observer2, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer1);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer1);
+        watchdog.registerHealthObserver(mTestExecutor, observer2);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), LONG_DURATION, observer2);
         moveTimeForwardAndDispatch(SHORT_DURATION);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -344,14 +343,14 @@
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
         // Start observing APP_A
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer);
 
         // Then advance time half-way
         moveTimeForwardAndDispatch(SHORT_DURATION / 2);
 
         // Start observing APP_A again
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer);
 
         // Then advance time such that it should have expired were it not for the second observation
         moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1);
@@ -373,17 +372,17 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog1.registerHealthObserver(observer1, mTestExecutor);
-        watchdog1.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog1.registerHealthObserver(observer2, mTestExecutor);
-        watchdog1.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+        watchdog1.registerHealthObserver(mTestExecutor, observer1);
+        watchdog1.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer1);
+        watchdog1.registerHealthObserver(mTestExecutor, observer2);
+        watchdog1.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B), SHORT_DURATION, observer2);
         // Then advance time and run IO Handler so file is saved
         mTestLooper.dispatchAll();
         // Then start a new watchdog
         PackageWatchdog watchdog2 = createWatchdog();
         // Then resume observer1 and observer2
-        watchdog2.registerHealthObserver(observer1, mTestExecutor);
-        watchdog2.registerHealthObserver(observer2, mTestExecutor);
+        watchdog2.registerHealthObserver(mTestExecutor, observer1);
+        watchdog2.registerHealthObserver(mTestExecutor, observer2);
         raiseFatalFailureAndDispatch(watchdog2,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
                         new VersionedPackage(APP_B, VERSION_CODE)),
@@ -405,10 +404,10 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog.registerHealthObserver(observer2, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.registerHealthObserver(observer1, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer2);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer2);
+        watchdog.registerHealthObserver(mTestExecutor, observer1);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer1);
 
         // Then fail APP_A below the threshold
         for (int i = 0; i < watchdog.getTriggerFailureCount() - 1; i++) {
@@ -434,10 +433,10 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog.registerHealthObserver(observer2, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.registerHealthObserver(observer1, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_B), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer2);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer2);
+        watchdog.registerHealthObserver(mTestExecutor, observer1);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_B), SHORT_DURATION, observer1);
 
         // Then fail APP_C (not observed) above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -469,8 +468,8 @@
                 }
             };
 
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer);
 
         // Then fail APP_A (different version) above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -499,18 +498,17 @@
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         // Start observing for all impact observers
-        watchdog.registerHealthObserver(observerNone, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
-                SHORT_DURATION);
-        watchdog.registerHealthObserver(observerHigh, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
-                SHORT_DURATION);
-        watchdog.registerHealthObserver(observerMid, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B),
-                SHORT_DURATION);
-        watchdog.registerHealthObserver(observerLow, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A),
-                SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observerNone);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+                SHORT_DURATION, observerNone);
+        watchdog.registerHealthObserver(mTestExecutor, observerHigh);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B, APP_C), SHORT_DURATION,
+                observerHigh);
+        watchdog.registerHealthObserver(mTestExecutor, observerMid);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B), SHORT_DURATION,
+                observerMid);
+        watchdog.registerHealthObserver(mTestExecutor, observerLow);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observerLow);
 
         // Then fail all apps above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -549,18 +547,17 @@
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
 
         // Start observing for all impact observers
-        watchdog.registerHealthObserver(observerNone, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
-                SHORT_DURATION);
-        watchdog.registerHealthObserver(observerHigh, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
-                SHORT_DURATION);
-        watchdog.registerHealthObserver(observerMid, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B),
-                SHORT_DURATION);
-        watchdog.registerHealthObserver(observerLow, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A),
-                SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observerNone);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+                SHORT_DURATION, observerNone);
+        watchdog.registerHealthObserver(mTestExecutor, observerHigh);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B, APP_C), SHORT_DURATION,
+                observerHigh);
+        watchdog.registerHealthObserver(mTestExecutor, observerMid);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B), SHORT_DURATION,
+                observerMid);
+        watchdog.registerHealthObserver(mTestExecutor, observerLow);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observerLow);
 
         // Then fail all apps above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -607,10 +604,10 @@
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing for observerFirst and observerSecond with failure handling
-        watchdog.registerHealthObserver(observerFirst, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
-        watchdog.registerHealthObserver(observerSecond, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observerFirst);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), LONG_DURATION, observerFirst);
+        watchdog.registerHealthObserver(mTestExecutor, observerSecond);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), LONG_DURATION, observerSecond);
 
         // Then fail APP_A above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -673,10 +670,10 @@
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
 
         // Start observing for observerFirst and observerSecond with failure handling
-        watchdog.registerHealthObserver(observerFirst, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
-        watchdog.registerHealthObserver(observerSecond, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observerFirst);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), LONG_DURATION, observerFirst);
+        watchdog.registerHealthObserver(mTestExecutor, observerSecond);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), LONG_DURATION, observerSecond);
 
         // Then fail APP_A above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -743,10 +740,10 @@
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
 
         // Start observing for observer1 and observer2 with failure handling
-        watchdog.registerHealthObserver(observer2, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.registerHealthObserver(observer1, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer2);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer2);
+        watchdog.registerHealthObserver(mTestExecutor, observer1);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer1);
 
         // Then fail APP_A above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -767,10 +764,10 @@
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
 
         // Start observing for observer1 and observer2 with failure handling
-        watchdog.registerHealthObserver(observer2, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.registerHealthObserver(observer1, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer2);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer2);
+        watchdog.registerHealthObserver(mTestExecutor, observer1);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer1);
 
         // Then fail APP_A above the threshold
         raiseFatalFailureAndDispatch(watchdog,
@@ -800,10 +797,10 @@
         // Start observing with explicit health checks for APP_A and APP_B respectively
         // with observer1 and observer2
         controller.setSupportedPackages(Arrays.asList(APP_A, APP_B));
-        watchdog.registerHealthObserver(observer1, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.registerHealthObserver(observer2, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer1);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer1);
+        watchdog.registerHealthObserver(mTestExecutor, observer2);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_B), SHORT_DURATION, observer2);
 
         // Run handler so requests are dispatched to the controller
         mTestLooper.dispatchAll();
@@ -819,8 +816,8 @@
         // Observer3 didn't exist when we got the explicit health check above, so
         // it starts out with a non-passing explicit health check and has to wait for a pass
         // otherwise it would be notified of APP_A failure on expiry
-        watchdog.registerHealthObserver(observer3, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer3, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer3);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer3);
 
         // Then expire observers
         moveTimeForwardAndDispatch(SHORT_DURATION);
@@ -850,9 +847,9 @@
 
         // Start observing with explicit health checks for APP_A and APP_B
         controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_B), LONG_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_B), LONG_DURATION, observer);
 
         // Run handler so requests are dispatched to the controller
         mTestLooper.dispatchAll();
@@ -888,7 +885,7 @@
         // Then set new supported packages
         controller.setSupportedPackages(Arrays.asList(APP_C));
         // Start observing APP_A and APP_C; only APP_C has support for explicit health checks
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_C), SHORT_DURATION, observer);
 
         // Run handler so requests/cancellations are dispatched to the controller
         mTestLooper.dispatchAll();
@@ -919,8 +916,8 @@
         // package observation duration == LONG_DURATION
         // health check duration == SHORT_DURATION (set by default in the TestController)
         controller.setSupportedPackages(Arrays.asList(APP_A));
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), LONG_DURATION, observer);
 
         // Then APP_A has exceeded health check duration
         moveTimeForwardAndDispatch(SHORT_DURATION);
@@ -951,8 +948,8 @@
         // package observation duration == SHORT_DURATION / 2
         // health check duration == SHORT_DURATION (set by default in the TestController)
         controller.setSupportedPackages(Arrays.asList(APP_A));
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION / 2, observer);
 
         // Forward time to expire the observation duration
         moveTimeForwardAndDispatch(SHORT_DURATION / 2);
@@ -1025,7 +1022,7 @@
         // Start observing with failure handling
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
-        wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+        wd.startExplicitHealthCheck(Collections.singletonList(APP_A), SHORT_DURATION, observer);
 
         // Notify of NetworkStack failure
         mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
@@ -1045,7 +1042,7 @@
         // Start observing with failure handling
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
-        wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+        wd.startExplicitHealthCheck(Collections.singletonList(APP_A), SHORT_DURATION, observer);
 
         // Notify of NetworkStack failure
         mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
@@ -1066,8 +1063,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer);
         // Fail APP_A below the threshold which should not trigger package failures
         for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
             watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -1095,8 +1092,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A, APP_B), Long.MAX_VALUE, observer);
         watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
@@ -1129,8 +1126,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), -1, observer);
         // Note: Don't move too close to the expiration time otherwise the handler will be thrashed
         // by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very
         // small timeouts.
@@ -1152,8 +1149,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), -1, observer);
         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1);
         raiseFatalFailureAndDispatch(watchdog,
                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -1175,8 +1172,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), Long.MAX_VALUE, observer);
         // Raise 2 failures at t=0 and t=900 respectively
         watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1203,10 +1200,10 @@
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        watchdog.registerHealthObserver(observer1, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.registerHealthObserver(observer2, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer1);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer1);
+        watchdog.registerHealthObserver(mTestExecutor, observer2);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_B), SHORT_DURATION, observer2);
 
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
@@ -1225,8 +1222,8 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
 
-        watchdog.registerHealthObserver(observer1, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer1);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_A), SHORT_DURATION, observer1);
 
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
@@ -1246,8 +1243,8 @@
         persistentObserver.setPersistent(true);
         persistentObserver.setMayObservePackages(true);
 
-        watchdog.registerHealthObserver(persistentObserver, mTestExecutor);
-        watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, persistentObserver);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_B), SHORT_DURATION, persistentObserver);
 
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1265,8 +1262,8 @@
         persistentObserver.setPersistent(true);
         persistentObserver.setMayObservePackages(false);
 
-        watchdog.registerHealthObserver(persistentObserver, mTestExecutor);
-        watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, persistentObserver);
+        watchdog.startExplicitHealthCheck(Arrays.asList(APP_B), SHORT_DURATION, persistentObserver);
 
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1277,11 +1274,10 @@
     /** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */
     @Test
     public void testBootLoopDetection_meetsThreshold() {
-        Slog.w("hrm1243", "I should definitely be here try 1 ");
         mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(bootObserver, mTestExecutor);
+        watchdog.registerHealthObserver(mTestExecutor, bootObserver);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
@@ -1293,7 +1289,7 @@
     public void testBootLoopDetection_meetsThresholdRecoverability() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(bootObserver, mTestExecutor);
+        watchdog.registerHealthObserver(mTestExecutor, bootObserver);
         for (int i = 0; i < 15; i++) {
             watchdog.noteBoot();
         }
@@ -1309,7 +1305,7 @@
     public void testBootLoopDetection_doesNotMeetThreshold() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(bootObserver, mTestExecutor);
+        watchdog.registerHealthObserver(mTestExecutor, bootObserver);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
             watchdog.noteBoot();
         }
@@ -1326,7 +1322,7 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
-        watchdog.registerHealthObserver(bootObserver, mTestExecutor);
+        watchdog.registerHealthObserver(mTestExecutor, bootObserver);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
             watchdog.noteBoot();
         }
@@ -1345,8 +1341,8 @@
         bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
         TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
         bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
-        watchdog.registerHealthObserver(bootObserver1, mTestExecutor);
-        watchdog.registerHealthObserver(bootObserver2, mTestExecutor);
+        watchdog.registerHealthObserver(mTestExecutor, bootObserver1);
+        watchdog.registerHealthObserver(mTestExecutor, bootObserver2);
         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
             watchdog.noteBoot();
         }
@@ -1362,8 +1358,8 @@
         bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
         TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
         bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
-        watchdog.registerHealthObserver(bootObserver1, mTestExecutor);
-        watchdog.registerHealthObserver(bootObserver2, mTestExecutor);
+        watchdog.registerHealthObserver(mTestExecutor, bootObserver1);
+        watchdog.registerHealthObserver(mTestExecutor, bootObserver2);
         for (int i = 0; i < 15; i++) {
             watchdog.noteBoot();
         }
@@ -1380,7 +1376,7 @@
         mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(bootObserver, mTestExecutor);
+        watchdog.registerHealthObserver(mTestExecutor, bootObserver);
         for (int i = 0; i < 4; i++) {
             for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; j++) {
                 watchdog.noteBoot();
@@ -1403,7 +1399,7 @@
         PackageWatchdog watchdog = createWatchdog();
         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
-        watchdog.registerHealthObserver(bootObserver, mTestExecutor);
+        watchdog.registerHealthObserver(mTestExecutor, bootObserver);
         for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; j++) {
             watchdog.noteBoot();
         }
@@ -1431,8 +1427,8 @@
     public void testNullFailedPackagesList() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(observer1, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer1, List.of(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer1);
+        watchdog.startExplicitHealthCheck(List.of(APP_A), LONG_DURATION, observer1);
 
         raiseFatalFailureAndDispatch(watchdog, null, PackageWatchdog.FAILURE_REASON_APP_CRASH);
         assertThat(observer1.mMitigatedPackages).isEmpty();
@@ -1450,18 +1446,18 @@
         PackageWatchdog watchdog = createWatchdog(testController, true);
 
         TestObserver testObserver1 = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(testObserver1, mTestExecutor);
-        watchdog.startExplicitHealthCheck(testObserver1, List.of(APP_A), LONG_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, testObserver1);
+        watchdog.startExplicitHealthCheck(List.of(APP_A), LONG_DURATION, testObserver1);
         mTestLooper.dispatchAll();
 
         TestObserver testObserver2 = new TestObserver(OBSERVER_NAME_2);
-        watchdog.registerHealthObserver(testObserver2, mTestExecutor);
-        watchdog.startExplicitHealthCheck(testObserver2, List.of(APP_B), LONG_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, testObserver2);
+        watchdog.startExplicitHealthCheck(List.of(APP_B), LONG_DURATION, testObserver2);
         mTestLooper.dispatchAll();
 
         TestObserver testObserver3 = new TestObserver(OBSERVER_NAME_3);
-        watchdog.registerHealthObserver(testObserver3, mTestExecutor);
-        watchdog.startExplicitHealthCheck(testObserver3, List.of(APP_C), LONG_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, testObserver3);
+        watchdog.startExplicitHealthCheck(List.of(APP_C), LONG_DURATION, testObserver3);
         mTestLooper.dispatchAll();
 
         watchdog.unregisterHealthObserver(testObserver1);
@@ -1493,15 +1489,15 @@
     public void testFailureHistoryIsPreserved() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, List.of(APP_A), SHORT_DURATION);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(List.of(APP_A), SHORT_DURATION, observer);
         for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
             watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
                     PackageWatchdog.FAILURE_REASON_UNKNOWN);
         }
         mTestLooper.dispatchAll();
         assertThat(observer.mMitigatedPackages).isEmpty();
-        watchdog.startExplicitHealthCheck(observer, List.of(APP_A), LONG_DURATION);
+        watchdog.startExplicitHealthCheck(List.of(APP_A), LONG_DURATION, observer);
         watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
         mTestLooper.dispatchAll();
@@ -1516,9 +1512,9 @@
     public void testMitigationSlidingWindow() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
-        watchdog.registerHealthObserver(observer, mTestExecutor);
-        watchdog.startExplicitHealthCheck(observer, List.of(APP_A),
-                PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS * 2);
+        watchdog.registerHealthObserver(mTestExecutor, observer);
+        watchdog.startExplicitHealthCheck(List.of(APP_A),
+                PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS * 2, observer);
 
 
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,